* [PATCH 0/2] Python Style API
@ 2025-06-04 9:39 Andrew Burgess
2025-06-04 9:39 ` [PATCH 1/2] gdb/python: add gdb.Style class Andrew Burgess
` (2 more replies)
0 siblings, 3 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-06-04 9:39 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
After the gdb.Color support was added to the Python API I wondered if
this would be enough to easily add styling to custom Python commands.
But I don't think it is, there's still lots that would need to be
handled in user code, for example, the intensity setting. And having
styles that track named styles (e.g. if I want to apply the 'filename'
style to some text from Python).
So this series builds on top of the gdb.Color API to provide a new
gdb.Style API which, I hope, makes it easy to start writing Python
commands that can use GDB's styles.
Here's an example of a simple custome command that uses styles:
import os
class user_home_cmd(gdb.Command):
def __init__(self):
super().__init__("user-home", gdb.COMMAND_USER)
self._style = gdb.Style('filename')
def invoke(self, args, from_tty):
print("The filename is %s."
% (self._style.apply(os.environ['HOME'])))
user_home_cmd()
The new 'user-home' command prints the current users home directory
using 'filename' style. Changing the 'filename' style and re-running
the command will track the style changes.
Thanks,
Andrew
---
Andrew Burgess (2):
gdb/python: add gdb.Style class
gdb/python: new class gdb.StyleParameterSet
gdb/Makefile.in | 1 +
gdb/NEWS | 11 +
gdb/doc/python.texi | 253 ++++++
gdb/python/lib/gdb/__init__.py | 209 +++++
gdb/python/py-style.c | 797 ++++++++++++++++++
.../gdb.python/py-style-parameter-set.exp | 361 ++++++++
gdb/testsuite/gdb.python/py-style.exp | 339 ++++++++
gdb/ui-style.h | 6 +
8 files changed, 1977 insertions(+)
create mode 100644 gdb/python/py-style.c
create mode 100644 gdb/testsuite/gdb.python/py-style-parameter-set.exp
create mode 100644 gdb/testsuite/gdb.python/py-style.exp
base-commit: f84a4db958af0ee1521acdf69eb2be38ea2e66a3
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCH 1/2] gdb/python: add gdb.Style class
2025-06-04 9:39 [PATCH 0/2] Python Style API Andrew Burgess
@ 2025-06-04 9:39 ` Andrew Burgess
2025-06-04 9:39 ` [PATCH 2/2] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
2025-06-06 13:38 ` [PATCHv2 0/2] Python Style API Andrew Burgess
2 siblings, 0 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-06-04 9:39 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
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.
---
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 ec71d899994..44e21c43071 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
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCH 2/2] gdb/python: new class gdb.StyleParameterSet
2025-06-04 9:39 [PATCH 0/2] Python Style API Andrew Burgess
2025-06-04 9:39 ` [PATCH 1/2] gdb/python: add gdb.Style class Andrew Burgess
@ 2025-06-04 9:39 ` Andrew Burgess
2025-06-06 13:38 ` [PATCHv2 0/2] Python Style API Andrew Burgess
2 siblings, 0 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-06-04 9:39 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
Add a new helper class gdb.StyleParameterSet. This new class can be
used to simplify creation of new style parameter sets. A style
parameter set is the 'foreground', 'background', and (optionally), the
'intensity' settings, all grouped under a single prefix command.
And example usage is:
(gdb) python s = gdb.StyleParameterSet("my-style")
(gdb) show style my-style
style my-style background: The "my-style" style background color is: none
style my-style foreground: The "my-style" style foreground color is: none
style my-style intensity: The "my-style" style display intensity is: normal
(gdb)
Having created a gdb.StyleParameterSet, the object itself can be used
to access a named style corresponding to the setting group, like this:
(gdb) python print(s.style)
<gdb.Style name='my-style', fg=none, bg=none, intensity=normal>
(gdb)
Of course, having access to the gdb.Style makes it easy to change the
settings, or the settings can be adjusted via the normal CLI 'set'
commands.
As gdb.StyleParameterSet manages a set of parameters, and the
gdb.Parameter class uses Parameter.value as the attribute to read the
parameter's value, there is also StyleParameterSet.value, but this is
just an alias for StyleParameterSet.style, that is, it allows the
gdb.Style object to be read and written too.
It is worth noting that this class only creates a single level of
prefix command. As an example GDB has style 'disassembler mnemonic',
where the 'disassembler' part is a group of related styles. If a user
wanted to create:
style
my-style-group
style-1
style-2
style-3
Where each of 'style-1', 'style-2', and 'style-3' will have the full
set of 'foreground', 'background', and 'intensity', then the
gdb.StyleParameterSet can be used to create the 'style-N' part, but
the user will have to create the 'my-style-group' prefix themselves,
possibly using gdb.ParameterPrefix, e.g.:
gdb.ParameterPrefix("style my-style-group", gdb.COMMAND_NONE)
gdb.StyleParameterSet("my-style-group style-1")
gdb.StyleParameterSet("my-style-group style-2")
gdb.StyleParameterSet("my-style-group style-3")
---
gdb/NEWS | 4 +
gdb/doc/python.texi | 115 +++++-
gdb/python/lib/gdb/__init__.py | 209 ++++++++++
.../gdb.python/py-style-parameter-set.exp | 361 ++++++++++++++++++
4 files changed, 688 insertions(+), 1 deletion(-)
create mode 100644 gdb/testsuite/gdb.python/py-style-parameter-set.exp
diff --git a/gdb/NEWS b/gdb/NEWS
index 44e21c43071..d6a868794c7 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -138,6 +138,10 @@ info threads [-gid] [-stopped] [-running] [ID]...
gdb.INTENSITY_DIM for use with gdb.Style when representing
intensities.
+ ** New gdb.StyleParameterSet for creating custom style settings.
+ Use gdb.StyleParameterSet(NAME) to create 'set style NAME ...'
+ and 'show style NAME ...' parameters.
+
** 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 db50dc5f635..04367c88e4b 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -5282,6 +5282,7 @@ Parameters In Python
feature-2}, then the @kbd{plugin-name} would need to be a prefix
command (@pxref{CLI Commands In Python}).
+@anchor{gdb.ParameterPrefix}
However, when creating parameters, you will almost always need to
create two prefix commands, one as a @kbd{set} sub-command, and one as
a @kbd{show} sub-command. @value{GDBN} provides the
@@ -5368,6 +5369,117 @@ Parameters In Python
ExampleParam("plugin-name feature-2")
@end smallexample
+@anchor{Creating Style Parameters}
+The helper class @code{gdb.StyleParameterSet} exists to make it easier
+to create new styles. @value{GDBN} places style settings under
+@samp{show style @dots{}} and @samp{set style @dots{}}, an example of a style
+is @samp{filename}. Each style is really a prefix command (@pxref{CLI
+Commands In Python}), with sub-commands @samp{foreground},
+@samp{background}, and optionally, @samp{intensity}.
+
+It is simple enough to create a new style using two @code{gdb.Command}
+objects for the prefix commands (one for @samp{set}, and one for
+@samp{show}), and three @code{gdb.Parameter} objects, one each for the
+@samp{foreground}, @samp{background}, and @samp{intensity}. You would
+also want to take care to craft the help text so that the new style
+behaves the same as the existing styles.
+
+Or, you can use the @code{gdb.StyleParameterSet} class, which takes
+care of all this, as the following example shows:
+
+@smallexample
+@group
+(@value{GDBP}) python s = gdb.StyleParameterSet("my-style")
+(@value{GDBP}) show style my-style
+style my-style background: The "my-style" style background color is: none
+style my-style foreground: The "my-style" style foreground color is: none
+style my-style intensity: The "my-style" style display intensity is: normal
+(@value{GDBP})
+@end group
+@end smallexample
+
+You might also want to group a number of styles within a new prefix,
+similar to how @value{GDBN} groups disassembler related styles within
+the @samp{style disassembler} prefix. This can be done using
+@code{gdb.ParameterPrefix} (@pxref{gdb.ParameterPrefix}), as in this
+example:
+
+@smallexample
+@group
+(@value{GDBP}) python gdb.ParameterPrefix("style my-group", gdb.COMMAND_NONE)
+(@value{GDBP}) python s_a = gdb.StyleParameterSet("my-group aaa")
+(@value{GDBP}) python s_b = gdb.StyleParameterSet("my-group bbb")
+(@value{GDBP}) show style my-group
+style my-group aaa background: The "my-group aaa" style background color is: none
+style my-group aaa foreground: The "my-group aaa" style foreground color is: none
+style my-group aaa intensity: The "my-group aaa" style display intensity is: normal
+style my-group bbb background: The "my-group bbb" style background color is: none
+style my-group bbb foreground: The "my-group bbb" style foreground color is: none
+style my-group bbb intensity: The "my-group bbb" style display intensity is: normal
+(@value{GDBP})
+@end group
+@end smallexample
+
+The @code{gdb.StyleParameterSet} class has the following methods and
+attributes:
+
+@defun StyleParameterSet.__init__(name, @w{add_intensity=@code{True}}, @w{doc=@code{None}})
+Create a new style group based on @var{name}, which is a string. For
+example if @var{name} is @samp{my-style}, then @value{GDBN} will
+create the prefix commands @samp{set style my-style} and @samp{show
+style my-style}. Within these prefix commands will be
+@samp{foreground}, @samp{background}, and @samp{intensity} parameters
+with the appropriate types.
+
+It is also possible for @var{name} to consist of multiple words, so
+long as each prefix command (except the last one) already exists. For
+example, it is valid to use a @var{name} value of @samp{disassembler
+my-style}, as the @samp{disassembler} prefix command already exists.
+@value{GDBN} would then create @samp{set style disassembler my-style}
+and @samp{show style disassembler my-style}, and within the
+@samp{my-style} prefixes will be the @samp{foreground},
+@samp{background}, and @samp{intensity} parameters with the
+appropriate types.
+
+Every style requires a @samp{foreground} and @samp{background}, but
+not every style needs an @samp{intensity}. If @var{add_intensity} is
+@code{True} (the default), then the @samp{intensity} parameter will be
+created. If @var{add_intensity} is @code{False}, then the
+@samp{intensity} parameter will not be created.
+
+If the @samp{intensity} parameter is not created, then the
+@code{gdb.Style} (@pxref{Styles In Python}) created from this
+@code{gdb.StyleParameterSet} will have @code{gdb.INTENSITY_NORMAL}.
+
+The @var{doc} should be a string which will be used as the help text
+for the @var{name} prefix command. This text is used as the
+@code{Command.__doc__} value for the @code{gdb.Command} object that is
+the prefix command object (@pxref{CLI Commands In Python}). If
+@var{doc} is @code{None} (the default) then a basic default help text
+is used.
+@end defun
+
+@defun StyleParameterSet.apply(string)
+Equivalent to @code{StyleParameterSet.style.apply(string)}. Returns a
+copy of @var{string} with escape sequences added to the start and end.
+The escape sequence at the start applies this style, and the escape
+sequence at the end restores the terminal default.
+
+If styling is disabled (i.e.@: @samp{set style enabled off}), then no
+escape sequences are added and this method returns a copy of
+@var{string}.
+@end defun
+
+@defvar StyleParameterSet.style
+This read/write attribute holds a @code{gdb.Style} object
+(@pxref{Styles In Python}), that is a named style associated with this
+style parameter group.
+@end defvar
+
+@defvar StyleParameterSet.value
+This is an alias for @code{StyleParameterSet.style}, see above.
+@end defvar
+
@node Functions In Python
@subsubsection Writing new convenience functions
@@ -7264,7 +7376,8 @@ Styles In Python
@value{GDBN} has many styles builtin (@pxref{Output Styling}), and
style objects can be created that apply these builtin styles to Python
-output.
+output. It is also possible to create new styles which can be used to
+style Python output (@pxref{Creating Style Parameters}).
The style class is called @code{gdb.Style}, and has the following
methods and attributes:
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index cedd897ab0f..f2f57280007 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -515,3 +515,212 @@ class ParameterPrefix:
def dont_repeat(self):
if self.active_prefix is not None:
self.active_prefix.dont_repeat()
+
+
+class StyleParameterSet:
+ """Create new style parameters.
+
+ A style parameter is a set of parameters that start with 'set style ...'
+ and 'show style ...'. For example 'filename' is a style parameter, and
+ 'disassembler symbol' is another style parameter.
+
+ The name of the style parameter is really a prefix command. Under this
+ we must have two commands 'foreground' and 'background', which are color
+ parameters. A third, optional command 'intensity', is an enum with
+ values 'normal', 'bold', and 'dim'.
+
+ A StyleParameterSet is initialised with a name, e.g. 'filename' or
+ 'disassembler symbol'. The StyleParameterSet creates the prefix
+ commands in the 'set style' and 'show style' name space, and then adds
+ the 'foreground', 'background', and optionally, the 'intensity'
+ commands.
+
+ If you want a whole new style group, similar to 'disassembler',
+ then you need to add this yourself first, then StyleParameterSet
+ can be used to create styles within the new prefix group.
+
+ The 'value' attribute on this object can be used to get and set a
+ gdb.Style object which controls all aspects of this style.
+
+ For readability, the alias 'style' is the same as 'value'.
+ """
+
+ def __init__(self, name, add_intensity=True, doc=None):
+ # The STYLE_NAME is something like 'filename' is 'set style
+ # filename ...', and PARAM_NAME is one of 'foreground',
+ # 'background', or 'intensity'. The DESC_TEXT is the long
+ # form used in help text, like 'foreground color' or 'display
+ # intensity'. The DEFAULT_VALUE is used to set the SELF.value
+ # attribute. And PARAM_TYPE is a gdb.PARAM_* constant. The
+ # ARGS is used for gdb.PARAM_ENUM, which ARGS should be the
+ # enum value list.
+ class style_parameter(Parameter):
+ def __init__(
+ self,
+ style_name,
+ parent_obj,
+ param_name,
+ desc_text,
+ default_value,
+ param_type,
+ *args
+ ):
+ # Setup documentation must be done before calling
+ # parent's __init__ method, as the __init__ reads (and
+ # copies) these values.
+ self.show_doc = "Show the " + desc_text + " for this property."
+ self.set_doc = "Set the " + desc_text + " for this property."
+ self.__doc__ = ""
+
+ # Call the parent's __init__ method to actually create
+ # the parameter.
+ super().__init__(
+ "style " + style_name + " " + param_name,
+ COMMAND_NONE,
+ param_type,
+ *args
+ )
+
+ # Store information we need in other methods.
+ self._style_name = style_name
+ self._desc_text = desc_text
+ self._parent = parent_obj
+
+ # Finally, setup the default value.
+ self.value = default_value
+
+ # Return the 'show style <style-name> <attribute>' string,
+ # which has styling applied.
+ def get_show_string(self, value):
+ s = self._parent.style
+ return (
+ "The "
+ + s.apply('"' + self._style_name + '" style')
+ + " "
+ + self._desc_text
+ + " is: "
+ + value
+ )
+
+ class style_foreground_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "foreground",
+ "foreground color",
+ Color(),
+ PARAM_COLOR,
+ )
+
+ class style_background_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "background",
+ "background color",
+ Color(),
+ PARAM_COLOR,
+ )
+
+ class style_intensity_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "intensity",
+ "display intensity",
+ "normal",
+ PARAM_ENUM,
+ ["normal", "bold", "dim"],
+ )
+
+ if doc is None:
+ doc = (
+ "The "
+ + name
+ + " display styling.\nConfigure "
+ + name
+ + " colors and display intensity."
+ )
+
+ ParameterPrefix("style " + name, COMMAND_NONE, doc)
+ self._foreground = style_foreground_parameter(name, self)
+ self._background = style_background_parameter(name, self)
+ if add_intensity:
+ self._intensity = style_intensity_parameter(name, self)
+ self._name = name
+ self._style = None
+
+ @property
+ def value(self):
+ """Return the gdb.Style object for this parameter set."""
+ if self._style is None:
+ self._style = Style(self._name)
+ return self._style
+
+ @property
+ def style(self):
+ """Return the gdb.Style object for this parameter set.
+
+ This is an alias for self.value."""
+ return self.value
+
+ @value.setter
+ def value(self, new_value):
+ """Set this parameter set to NEW_VALUE, a gdb.Style object.
+
+ The attributes of NEW_VALUE are used to update the current settings
+ of this parameter set. If this parameter set was created without
+ an intensity setting, then the intensity of NEW_VALUE is ignored."""
+ if not isinstance(new_value, Style):
+ raise TypeError("value must be gdb.Style, not %s" % type(new_value))
+ self._foreground.value = new_value.foreground
+ self._background.value = new_value.background
+ if hasattr(self, "_intensity"):
+ intensity_value = new_value.intensity
+ if intensity_value == INTENSITY_BOLD:
+ intensity_string = "bold"
+ elif intensity_value == INTENSITY_DIM:
+ intensity_string = "dim"
+ elif intensity_value == INTENSITY_NORMAL:
+ intensity_string = "normal"
+ else:
+ raise ValueError(
+ "unknown intensity value %d from Style" % intensity_value
+ )
+
+ self._intensity.value = intensity_string
+
+ @style.setter
+ def style(self, new_value):
+ """Set this parameter set to NEW_VALUE, a gdb.Style object.
+
+ This is an alias for self.value."""
+ self.value = new_value
+
+ def apply(self, *args, **kwargs):
+ """Apply this style to the arguments.
+
+ Forwards all arguments to self.style.apply(). The arguments should be a string,
+ to which this style is applied. This function returns the same string with
+ escape sequences added to apply this style.
+
+ If styling is globally disabled ('set style enabled off') then no escape sequences
+ will be addedded, the input string is returned."""
+ return self.style.apply(*args, **kwargs)
+
+ def __repr__(self):
+ """A string representation of SELF."""
+
+ def full_typename(obj):
+ module = type(obj).__module__
+ qualname = type(obj).__qualname__
+
+ if module is None or module == "builtins":
+ return qualname
+ else:
+ return module + "." + qualname
+
+ return "<" + full_typename(self) + " name='" + self._name + "'>"
diff --git a/gdb/testsuite/gdb.python/py-style-parameter-set.exp b/gdb/testsuite/gdb.python/py-style-parameter-set.exp
new file mode 100644
index 00000000000..2544ce127ce
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-style-parameter-set.exp
@@ -0,0 +1,361 @@
+# 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.StyleParameterSet.
+
+load_lib gdb-python.exp
+
+require allow_python_tests
+
+# Create a regexp that can be used to match the output of a 'show style
+# NAME' command. HAS_INTENSITY is a boolean and indicates if style NAME has
+# an intensity attribute.
+proc gen_show_style_re { name has_intensity } {
+ set output \
+ [list \
+ "style ${name} background: The \"${name}\" style background color is: none" \
+ "style ${name} foreground: The \"${name}\" style foreground color is: none"]
+
+ if { $has_intensity } {
+ lappend output \
+ "style ${name} intensity: The \"${name}\" style display intensity is: normal"
+ }
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'set style NAME' command,
+# that is, a 'set' command that doesn't actually set an attribute of a
+# style, in this case GDB will print some useful help text. HAS_INTENSITY is
+# a boolean and indicates if style NAME has an intensity attribute or not.
+proc gen_set_style_re { name has_intensity } {
+ set output \
+ [list \
+ "List of \"set style $name\" subcommands:" \
+ "" \
+ "set style $name background -- Set the background color for this property\\." \
+ "set style $name foreground -- Set the foreground color for this property\\."]
+
+ if { $has_intensity } {
+ lappend output \
+ "set style $name intensity -- Set the display intensity for this property\\."
+ }
+
+ lappend output \
+ "" \
+ "Type \"help set style $name\" followed by subcommand name for full documentation\\." \
+ "Type \"apropos word\" to search for commands related to \"word\"\\." \
+ "Type \"apropos -v word\" for full documentation of commands related to \"word\"\\." \
+ "Command name abbreviations are allowed if unambiguous\\."
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'help show style NAME'
+# command. HAS_INTENSITY is a boolean and indicates if style NAME has an
+# intensity attribute or not. DOC is a regexp that matches the doc string
+# for style NAME.
+proc gen_help_show_style_re { name has_intensity doc } {
+ set output \
+ [list \
+ $doc \
+ "" \
+ "List of \"show style ${name}\" subcommands:" \
+ "" \
+ "show style $name background -- Show the background color for this property\\." \
+ "show style $name foreground -- Show the foreground color for this property\\."]
+ if { $has_intensity } {
+ lappend output \
+ "show style $name intensity -- Show the display intensity for this property\\."
+ }
+
+ lappend output \
+ "" \
+ "Type \"help show style $name\" followed by subcommand name for full documentation\\." \
+ "Type \"apropos word\" to search for commands related to \"word\"\\." \
+ "Type \"apropos -v word\" for full documentation of commands related to \"word\"\\." \
+ "Command name abbreviations are allowed if unambiguous\\."
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'help set style NAME'
+# command. HAS_INTENSITY is a boolean and indicates if style NAME has an
+# intensity attribute or not. DOC is a regexp that matches the doc string
+# for style NAME.
+proc gen_help_set_style_re { name has_intensity doc } {
+ return \
+ [multi_line \
+ $doc \
+ "" \
+ [gen_set_style_re $name $has_intensity]]
+}
+
+# Create styles with and without intensity. Use named and unnamed
+# argument passing, and vary the argument passing order. Create
+# styles with and without documentation.
+#
+# Confirm that the styles contain the expected sub-commands, and that
+# the documentation is as expected.
+proc_with_prefix test_basic_usage {} {
+ clean_restart
+
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"style-1\")" \
+ "create style-1"
+
+ gdb_test_no_output \
+ "python style_2 = gdb.StyleParameterSet(add_intensity=True, name=\"style-2\")" \
+ "create style-2"
+
+ gdb_test_no_output \
+ "python style_3 = gdb.StyleParameterSet(name=\"style-3\", doc=\"Style-3 display styling.\\nThis is a multi-line documentation\\nstring describing style-3.\")" \
+ "create style-3"
+
+ gdb_test_no_output \
+ "python style_4 = gdb.StyleParameterSet(\"style-4\", add_intensity=False)" \
+ "create style-4"
+
+ foreach style { style-1 style-2 style-3 } {
+ gdb_test "show style $style" \
+ [gen_show_style_re $style true]
+ gdb_test "set style $style" \
+ [gen_set_style_re $style true]
+ }
+
+ gdb_test "show style style-4" \
+ [gen_show_style_re "style-4" false]
+
+ foreach style { style-1 style-2 } {
+ gdb_test "help show style $style" \
+ [gen_help_show_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+
+
+ set out [gen_help_set_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+
+ gdb_test "help set style $style" \
+ [gen_help_set_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+ }
+
+ gdb_test "help show style style-3" \
+ [gen_help_show_style_re "style-3" true \
+ [multi_line \
+ "Style-3 display styling\\." \
+ "This is a multi-line documentation" \
+ "string describing style-3\\."]]
+
+ gdb_test "help show style style-4" \
+ [gen_help_show_style_re "style-4" false \
+ [multi_line \
+ "The style-4 display styling\\." \
+ "Configure style-4 colors and display intensity\\."]]
+
+ for { set i 1 } { $i < 5 } { incr i } {
+ gdb_test "python print(style_$i)" \
+ "<gdb.StyleParameterSet name='style-$i'>" \
+ "print repr of style_$i"
+ }
+
+ # There is no 'style-4 intensity' property.
+ gdb_test "show style style-4 foreground" \
+ "The \"style-4\" style foreground color is: none"
+ gdb_test "show style style-4 background" \
+ "The \"style-4\" style background color is: none"
+ gdb_test "show style style-4 intensity" \
+ "Undefined show style style-4 command: \"intensity\"\\. Try \"help show style style-4\"\\."
+
+ # There is a 'style-1 intensity' property.
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: none" \
+ "show style-1 foreground before changes"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: none" \
+ "show style-1 background before changes"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: normal" \
+ "show style-1 intensity before changes"
+
+ # Grab the gdb.Style objects from 'style-1'.
+ gdb_test_no_output "python s1 = style_1.style"
+ gdb_test_no_output "python s2 = style_1.value"
+
+ # Check both represent the same style.
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=none, bg=none, intensity=normal>" \
+ "print s1 style before changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=none, bg=none, intensity=normal>" \
+ "print s2 style before changes"
+
+ gdb_test_no_output \
+ "python s1.foreground=gdb.Color('red')" "set foreground"
+ gdb_test_no_output \
+ "python s1.background=gdb.Color('blue')" "set background"
+ gdb_test_no_output \
+ "python s1.intensity=gdb.INTENSITY_DIM" "set intensity"
+
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=red, bg=blue, intensity=dim>" \
+ "print s1 style after first set of changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=red, bg=blue, intensity=dim>" \
+ "print s2 style after first set of changes"
+
+ # Check the style properties have updated.
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: red" \
+ "show style-1 foreground after first set of changes"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: blue" \
+ "show style-1 background after first set of changes"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: dim" \
+ "show style-1 intensity after first set of changes"
+
+ # Change the style properties, check gdb.Style objects update.
+ gdb_test_no_output "set style style-1 foreground yellow"
+ gdb_test_no_output "set style style-1 background cyan"
+ gdb_test_no_output "set style style-1 intensity bold"
+
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=yellow, bg=cyan, intensity=bold>" \
+ "print s1 style after second set of changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=yellow, bg=cyan, intensity=bold>" \
+ "print s2 style after second set of changes"
+
+ # Assign a gdb.Style to set 'style-1'. First create some unnamed
+ # style objects that can be used.
+ gdb_test_no_output \
+ "python uns1 = gdb.Style(foreground=gdb.Color('white'), background=gdb.Color('black'), intensity=gdb.INTENSITY_BOLD)" \
+ "create uns1"
+ gdb_test_no_output \
+ "python uns2 = gdb.Style(foreground=gdb.Color('black'), background=gdb.Color('white'), intensity=gdb.INTENSITY_DIM)" \
+ "create uns2"
+
+ gdb_test_no_output "python style_1.value = uns1"
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: white" \
+ "show style-1 foreground after assigning uns1"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: black" \
+ "show style-1 background after assigning uns1"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: bold" \
+ "show style-1 intensity after assigning uns1"
+
+ gdb_test_no_output "python style_1.style = uns2"
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: black" \
+ "show style-1 foreground after assigning uns2"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: white" \
+ "show style-1 background after assigning uns2"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: dim" \
+ "show style-1 intensity after assigning uns2"
+
+ # Assign a style with an intensity that is not 'NORMAL' to a
+ # StyleParameterSet that doesn't have an intensity. The new
+ # intensity setting should be ignored.
+ gdb_test_no_output "python style_4.style = uns1"
+ gdb_test "show style style-4 foreground" \
+ "The \"style-4\" style foreground color is: white" \
+ "show style-4 foreground after assigning uns1"
+ gdb_test "show style style-4 background" \
+ "The \"style-4\" style background color is: black" \
+ "show style-4 background after assigning uns1"
+ gdb_test "show style style-4 intensity" \
+ "Undefined show style style-4 command: \"intensity\"\\. Try \"help show style style-4\"\\." \
+ "show style-4 intensity after assigning uns1"
+
+ gdb_test "python print(style_4.style)" \
+ "<gdb.Style name='style-4', fg=white, bg=black, intensity=normal>" \
+ "print string repr of style_4's style"
+}
+
+# Test creating a style prefix with gdb.ParameterPrefix, then adding
+# some styles within the new prefix. Change the style through the CLI
+# and confirm that the associated Python object updated as expected.
+proc_with_prefix test_style_prefix {} {
+ gdb_test_no_output \
+ "python gdb.ParameterPrefix(\"style my-style-group\", gdb.COMMAND_NONE)"
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"my-style-group style-1\")" \
+ "create style-1"
+ gdb_test_no_output \
+ "python style_2 = gdb.StyleParameterSet(\"my-style-group style-2\")" \
+ "create style-2"
+
+ gdb_test "python print(style_1.style)" \
+ "<gdb.Style name='my-style-group style-1', fg=none, bg=none, intensity=normal>" \
+ "print 'my-style-group style-1' style before changes"
+ gdb_test "python print(style_2.style)" \
+ "<gdb.Style name='my-style-group style-2', fg=none, bg=none, intensity=normal>" \
+ "print 'my-style-group style-2' style before changes"
+
+ gdb_test_no_output "set style my-style-group style-1 foreground red"
+ gdb_test_no_output "set style my-style-group style-1 background yellow"
+ gdb_test_no_output "set style my-style-group style-1 intensity bold"
+ gdb_test_no_output "set style my-style-group style-2 foreground black"
+ gdb_test_no_output "set style my-style-group style-2 background blue"
+ gdb_test_no_output "set style my-style-group style-2 intensity dim"
+
+ gdb_test "python print(style_1.style)" \
+ "<gdb.Style name='my-style-group style-1', fg=red, bg=yellow, intensity=bold>" \
+ "print 'my-style-group style-1' style after changes"
+ gdb_test "python print(style_2.style)" \
+ "<gdb.Style name='my-style-group style-2', fg=black, bg=blue, intensity=dim>" \
+ "print 'my-style-group style-2' style after changes"
+}
+
+# Test that gdb.StyleParameterSet.apply() works as expected.
+proc_with_prefix test_applying {} {
+ with_ansi_styling_terminal {
+ clean_restart
+ }
+
+ # Create a new StyleParameterSet, and adjust its settings.
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"style-1\")" \
+ "create style-1"
+ gdb_test_no_output \
+ "python uns1 = gdb.Style(foreground=gdb.Color('red'), background=gdb.Color('blue'), intensity=gdb.INTENSITY_BOLD)" \
+ "create uns1"
+ gdb_test_no_output "python style_1 = uns1"
+
+ # When styling is off, no escape sequences should be added.
+ gdb_test \
+ "with style enabled off -- python print(style_1.apply('xxx'))" "^xxx" \
+ "apply StyleParameterSet to a string when styling is off"
+
+ # When styling is on, we should see an escape sequence added.
+ gdb_test "python print(style_1.apply('xxx'))" \
+ "\033\\\[31;44;1;27mxxx\033\\\[m" \
+ "apply a style when styling is on"
+}
+
+test_basic_usage
+test_style_prefix
+test_applying
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv2 0/2] Python Style API
2025-06-04 9:39 [PATCH 0/2] Python Style API Andrew Burgess
2025-06-04 9:39 ` [PATCH 1/2] gdb/python: add gdb.Style class Andrew Burgess
2025-06-04 9:39 ` [PATCH 2/2] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
@ 2025-06-06 13:38 ` Andrew Burgess
2025-06-06 13:38 ` [PATCHv2 1/2] gdb/python: add gdb.Style class Andrew Burgess
` (2 more replies)
2 siblings, 3 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-06-06 13:38 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
In v2:
- Fixed an issue with the py-style-parameter-set.exp test; GDB was
not started in a terminal with styling support in one case, so
tests would fail depending on how the testsuite was being run.
---
After the gdb.Color support was added to the Python API I wondered if
this would be enough to easily add styling to custom Python commands.
But I don't think it is, there's still lots that would need to be
handled in user code, for example, the intensity setting. And having
styles that track named styles (e.g. if I want to apply the 'filename'
style to some text from Python).
So this series builds on top of the gdb.Color API to provide a new
gdb.Style API which, I hope, makes it easy to start writing Python
commands that can use GDB's styles.
Here's an example of a simple custome command that uses styles:
import os
class user_home_cmd(gdb.Command):
def __init__(self):
super().__init__("user-home", gdb.COMMAND_USER)
self._style = gdb.Style('filename')
def invoke(self, args, from_tty):
print("The filename is %s."
% (self._style.apply(os.environ['HOME'])))
user_home_cmd()
The new 'user-home' command prints the current users home directory
using 'filename' style. Changing the 'filename' style and re-running
the command will track the style changes.
Thanks,
Andrew
---
Andrew Burgess (2):
gdb/python: add gdb.Style class
gdb/python: new class gdb.StyleParameterSet
gdb/Makefile.in | 1 +
gdb/NEWS | 11 +
gdb/doc/python.texi | 253 ++++++
gdb/python/lib/gdb/__init__.py | 209 +++++
gdb/python/py-style.c | 797 ++++++++++++++++++
.../gdb.python/py-style-parameter-set.exp | 366 ++++++++
gdb/testsuite/gdb.python/py-style.exp | 339 ++++++++
gdb/ui-style.h | 6 +
8 files changed, 1982 insertions(+)
create mode 100644 gdb/python/py-style.c
create mode 100644 gdb/testsuite/gdb.python/py-style-parameter-set.exp
create mode 100644 gdb/testsuite/gdb.python/py-style.exp
base-commit: f84a4db958af0ee1521acdf69eb2be38ea2e66a3
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv2 1/2] gdb/python: add gdb.Style class
2025-06-06 13:38 ` [PATCHv2 0/2] Python Style API Andrew Burgess
@ 2025-06-06 13:38 ` Andrew Burgess
2025-06-06 14:21 ` Eli Zaretskii
2025-06-06 13:38 ` [PATCHv2 2/2] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
2025-06-07 10:44 ` [PATCHv3 0/2] Python Style API Andrew Burgess
2 siblings, 1 reply; 61+ messages in thread
From: Andrew Burgess @ 2025-06-06 13:38 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
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.
---
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 ec71d899994..44e21c43071 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
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv2 2/2] gdb/python: new class gdb.StyleParameterSet
2025-06-06 13:38 ` [PATCHv2 0/2] Python Style API Andrew Burgess
2025-06-06 13:38 ` [PATCHv2 1/2] gdb/python: add gdb.Style class Andrew Burgess
@ 2025-06-06 13:38 ` Andrew Burgess
2025-06-07 10:44 ` [PATCHv3 0/2] Python Style API Andrew Burgess
2 siblings, 0 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-06-06 13:38 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
Add a new helper class gdb.StyleParameterSet. This new class can be
used to simplify creation of new style parameter sets. A style
parameter set is the 'foreground', 'background', and (optionally), the
'intensity' settings, all grouped under a single prefix command.
And example usage is:
(gdb) python s = gdb.StyleParameterSet("my-style")
(gdb) show style my-style
style my-style background: The "my-style" style background color is: none
style my-style foreground: The "my-style" style foreground color is: none
style my-style intensity: The "my-style" style display intensity is: normal
(gdb)
Having created a gdb.StyleParameterSet, the object itself can be used
to access a named style corresponding to the setting group, like this:
(gdb) python print(s.style)
<gdb.Style name='my-style', fg=none, bg=none, intensity=normal>
(gdb)
Of course, having access to the gdb.Style makes it easy to change the
settings, or the settings can be adjusted via the normal CLI 'set'
commands.
As gdb.StyleParameterSet manages a set of parameters, and the
gdb.Parameter class uses Parameter.value as the attribute to read the
parameter's value, there is also StyleParameterSet.value, but this is
just an alias for StyleParameterSet.style, that is, it allows the
gdb.Style object to be read and written too.
It is worth noting that this class only creates a single level of
prefix command. As an example GDB has style 'disassembler mnemonic',
where the 'disassembler' part is a group of related styles. If a user
wanted to create:
style
my-style-group
style-1
style-2
style-3
Where each of 'style-1', 'style-2', and 'style-3' will have the full
set of 'foreground', 'background', and 'intensity', then the
gdb.StyleParameterSet can be used to create the 'style-N' part, but
the user will have to create the 'my-style-group' prefix themselves,
possibly using gdb.ParameterPrefix, e.g.:
gdb.ParameterPrefix("style my-style-group", gdb.COMMAND_NONE)
gdb.StyleParameterSet("my-style-group style-1")
gdb.StyleParameterSet("my-style-group style-2")
gdb.StyleParameterSet("my-style-group style-3")
---
gdb/NEWS | 4 +
gdb/doc/python.texi | 115 +++++-
gdb/python/lib/gdb/__init__.py | 209 ++++++++++
.../gdb.python/py-style-parameter-set.exp | 366 ++++++++++++++++++
4 files changed, 693 insertions(+), 1 deletion(-)
create mode 100644 gdb/testsuite/gdb.python/py-style-parameter-set.exp
diff --git a/gdb/NEWS b/gdb/NEWS
index 44e21c43071..d6a868794c7 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -138,6 +138,10 @@ info threads [-gid] [-stopped] [-running] [ID]...
gdb.INTENSITY_DIM for use with gdb.Style when representing
intensities.
+ ** New gdb.StyleParameterSet for creating custom style settings.
+ Use gdb.StyleParameterSet(NAME) to create 'set style NAME ...'
+ and 'show style NAME ...' parameters.
+
** 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 db50dc5f635..04367c88e4b 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -5282,6 +5282,7 @@ Parameters In Python
feature-2}, then the @kbd{plugin-name} would need to be a prefix
command (@pxref{CLI Commands In Python}).
+@anchor{gdb.ParameterPrefix}
However, when creating parameters, you will almost always need to
create two prefix commands, one as a @kbd{set} sub-command, and one as
a @kbd{show} sub-command. @value{GDBN} provides the
@@ -5368,6 +5369,117 @@ Parameters In Python
ExampleParam("plugin-name feature-2")
@end smallexample
+@anchor{Creating Style Parameters}
+The helper class @code{gdb.StyleParameterSet} exists to make it easier
+to create new styles. @value{GDBN} places style settings under
+@samp{show style @dots{}} and @samp{set style @dots{}}, an example of a style
+is @samp{filename}. Each style is really a prefix command (@pxref{CLI
+Commands In Python}), with sub-commands @samp{foreground},
+@samp{background}, and optionally, @samp{intensity}.
+
+It is simple enough to create a new style using two @code{gdb.Command}
+objects for the prefix commands (one for @samp{set}, and one for
+@samp{show}), and three @code{gdb.Parameter} objects, one each for the
+@samp{foreground}, @samp{background}, and @samp{intensity}. You would
+also want to take care to craft the help text so that the new style
+behaves the same as the existing styles.
+
+Or, you can use the @code{gdb.StyleParameterSet} class, which takes
+care of all this, as the following example shows:
+
+@smallexample
+@group
+(@value{GDBP}) python s = gdb.StyleParameterSet("my-style")
+(@value{GDBP}) show style my-style
+style my-style background: The "my-style" style background color is: none
+style my-style foreground: The "my-style" style foreground color is: none
+style my-style intensity: The "my-style" style display intensity is: normal
+(@value{GDBP})
+@end group
+@end smallexample
+
+You might also want to group a number of styles within a new prefix,
+similar to how @value{GDBN} groups disassembler related styles within
+the @samp{style disassembler} prefix. This can be done using
+@code{gdb.ParameterPrefix} (@pxref{gdb.ParameterPrefix}), as in this
+example:
+
+@smallexample
+@group
+(@value{GDBP}) python gdb.ParameterPrefix("style my-group", gdb.COMMAND_NONE)
+(@value{GDBP}) python s_a = gdb.StyleParameterSet("my-group aaa")
+(@value{GDBP}) python s_b = gdb.StyleParameterSet("my-group bbb")
+(@value{GDBP}) show style my-group
+style my-group aaa background: The "my-group aaa" style background color is: none
+style my-group aaa foreground: The "my-group aaa" style foreground color is: none
+style my-group aaa intensity: The "my-group aaa" style display intensity is: normal
+style my-group bbb background: The "my-group bbb" style background color is: none
+style my-group bbb foreground: The "my-group bbb" style foreground color is: none
+style my-group bbb intensity: The "my-group bbb" style display intensity is: normal
+(@value{GDBP})
+@end group
+@end smallexample
+
+The @code{gdb.StyleParameterSet} class has the following methods and
+attributes:
+
+@defun StyleParameterSet.__init__(name, @w{add_intensity=@code{True}}, @w{doc=@code{None}})
+Create a new style group based on @var{name}, which is a string. For
+example if @var{name} is @samp{my-style}, then @value{GDBN} will
+create the prefix commands @samp{set style my-style} and @samp{show
+style my-style}. Within these prefix commands will be
+@samp{foreground}, @samp{background}, and @samp{intensity} parameters
+with the appropriate types.
+
+It is also possible for @var{name} to consist of multiple words, so
+long as each prefix command (except the last one) already exists. For
+example, it is valid to use a @var{name} value of @samp{disassembler
+my-style}, as the @samp{disassembler} prefix command already exists.
+@value{GDBN} would then create @samp{set style disassembler my-style}
+and @samp{show style disassembler my-style}, and within the
+@samp{my-style} prefixes will be the @samp{foreground},
+@samp{background}, and @samp{intensity} parameters with the
+appropriate types.
+
+Every style requires a @samp{foreground} and @samp{background}, but
+not every style needs an @samp{intensity}. If @var{add_intensity} is
+@code{True} (the default), then the @samp{intensity} parameter will be
+created. If @var{add_intensity} is @code{False}, then the
+@samp{intensity} parameter will not be created.
+
+If the @samp{intensity} parameter is not created, then the
+@code{gdb.Style} (@pxref{Styles In Python}) created from this
+@code{gdb.StyleParameterSet} will have @code{gdb.INTENSITY_NORMAL}.
+
+The @var{doc} should be a string which will be used as the help text
+for the @var{name} prefix command. This text is used as the
+@code{Command.__doc__} value for the @code{gdb.Command} object that is
+the prefix command object (@pxref{CLI Commands In Python}). If
+@var{doc} is @code{None} (the default) then a basic default help text
+is used.
+@end defun
+
+@defun StyleParameterSet.apply(string)
+Equivalent to @code{StyleParameterSet.style.apply(string)}. Returns a
+copy of @var{string} with escape sequences added to the start and end.
+The escape sequence at the start applies this style, and the escape
+sequence at the end restores the terminal default.
+
+If styling is disabled (i.e.@: @samp{set style enabled off}), then no
+escape sequences are added and this method returns a copy of
+@var{string}.
+@end defun
+
+@defvar StyleParameterSet.style
+This read/write attribute holds a @code{gdb.Style} object
+(@pxref{Styles In Python}), that is a named style associated with this
+style parameter group.
+@end defvar
+
+@defvar StyleParameterSet.value
+This is an alias for @code{StyleParameterSet.style}, see above.
+@end defvar
+
@node Functions In Python
@subsubsection Writing new convenience functions
@@ -7264,7 +7376,8 @@ Styles In Python
@value{GDBN} has many styles builtin (@pxref{Output Styling}), and
style objects can be created that apply these builtin styles to Python
-output.
+output. It is also possible to create new styles which can be used to
+style Python output (@pxref{Creating Style Parameters}).
The style class is called @code{gdb.Style}, and has the following
methods and attributes:
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index cedd897ab0f..f2f57280007 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -515,3 +515,212 @@ class ParameterPrefix:
def dont_repeat(self):
if self.active_prefix is not None:
self.active_prefix.dont_repeat()
+
+
+class StyleParameterSet:
+ """Create new style parameters.
+
+ A style parameter is a set of parameters that start with 'set style ...'
+ and 'show style ...'. For example 'filename' is a style parameter, and
+ 'disassembler symbol' is another style parameter.
+
+ The name of the style parameter is really a prefix command. Under this
+ we must have two commands 'foreground' and 'background', which are color
+ parameters. A third, optional command 'intensity', is an enum with
+ values 'normal', 'bold', and 'dim'.
+
+ A StyleParameterSet is initialised with a name, e.g. 'filename' or
+ 'disassembler symbol'. The StyleParameterSet creates the prefix
+ commands in the 'set style' and 'show style' name space, and then adds
+ the 'foreground', 'background', and optionally, the 'intensity'
+ commands.
+
+ If you want a whole new style group, similar to 'disassembler',
+ then you need to add this yourself first, then StyleParameterSet
+ can be used to create styles within the new prefix group.
+
+ The 'value' attribute on this object can be used to get and set a
+ gdb.Style object which controls all aspects of this style.
+
+ For readability, the alias 'style' is the same as 'value'.
+ """
+
+ def __init__(self, name, add_intensity=True, doc=None):
+ # The STYLE_NAME is something like 'filename' is 'set style
+ # filename ...', and PARAM_NAME is one of 'foreground',
+ # 'background', or 'intensity'. The DESC_TEXT is the long
+ # form used in help text, like 'foreground color' or 'display
+ # intensity'. The DEFAULT_VALUE is used to set the SELF.value
+ # attribute. And PARAM_TYPE is a gdb.PARAM_* constant. The
+ # ARGS is used for gdb.PARAM_ENUM, which ARGS should be the
+ # enum value list.
+ class style_parameter(Parameter):
+ def __init__(
+ self,
+ style_name,
+ parent_obj,
+ param_name,
+ desc_text,
+ default_value,
+ param_type,
+ *args
+ ):
+ # Setup documentation must be done before calling
+ # parent's __init__ method, as the __init__ reads (and
+ # copies) these values.
+ self.show_doc = "Show the " + desc_text + " for this property."
+ self.set_doc = "Set the " + desc_text + " for this property."
+ self.__doc__ = ""
+
+ # Call the parent's __init__ method to actually create
+ # the parameter.
+ super().__init__(
+ "style " + style_name + " " + param_name,
+ COMMAND_NONE,
+ param_type,
+ *args
+ )
+
+ # Store information we need in other methods.
+ self._style_name = style_name
+ self._desc_text = desc_text
+ self._parent = parent_obj
+
+ # Finally, setup the default value.
+ self.value = default_value
+
+ # Return the 'show style <style-name> <attribute>' string,
+ # which has styling applied.
+ def get_show_string(self, value):
+ s = self._parent.style
+ return (
+ "The "
+ + s.apply('"' + self._style_name + '" style')
+ + " "
+ + self._desc_text
+ + " is: "
+ + value
+ )
+
+ class style_foreground_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "foreground",
+ "foreground color",
+ Color(),
+ PARAM_COLOR,
+ )
+
+ class style_background_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "background",
+ "background color",
+ Color(),
+ PARAM_COLOR,
+ )
+
+ class style_intensity_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "intensity",
+ "display intensity",
+ "normal",
+ PARAM_ENUM,
+ ["normal", "bold", "dim"],
+ )
+
+ if doc is None:
+ doc = (
+ "The "
+ + name
+ + " display styling.\nConfigure "
+ + name
+ + " colors and display intensity."
+ )
+
+ ParameterPrefix("style " + name, COMMAND_NONE, doc)
+ self._foreground = style_foreground_parameter(name, self)
+ self._background = style_background_parameter(name, self)
+ if add_intensity:
+ self._intensity = style_intensity_parameter(name, self)
+ self._name = name
+ self._style = None
+
+ @property
+ def value(self):
+ """Return the gdb.Style object for this parameter set."""
+ if self._style is None:
+ self._style = Style(self._name)
+ return self._style
+
+ @property
+ def style(self):
+ """Return the gdb.Style object for this parameter set.
+
+ This is an alias for self.value."""
+ return self.value
+
+ @value.setter
+ def value(self, new_value):
+ """Set this parameter set to NEW_VALUE, a gdb.Style object.
+
+ The attributes of NEW_VALUE are used to update the current settings
+ of this parameter set. If this parameter set was created without
+ an intensity setting, then the intensity of NEW_VALUE is ignored."""
+ if not isinstance(new_value, Style):
+ raise TypeError("value must be gdb.Style, not %s" % type(new_value))
+ self._foreground.value = new_value.foreground
+ self._background.value = new_value.background
+ if hasattr(self, "_intensity"):
+ intensity_value = new_value.intensity
+ if intensity_value == INTENSITY_BOLD:
+ intensity_string = "bold"
+ elif intensity_value == INTENSITY_DIM:
+ intensity_string = "dim"
+ elif intensity_value == INTENSITY_NORMAL:
+ intensity_string = "normal"
+ else:
+ raise ValueError(
+ "unknown intensity value %d from Style" % intensity_value
+ )
+
+ self._intensity.value = intensity_string
+
+ @style.setter
+ def style(self, new_value):
+ """Set this parameter set to NEW_VALUE, a gdb.Style object.
+
+ This is an alias for self.value."""
+ self.value = new_value
+
+ def apply(self, *args, **kwargs):
+ """Apply this style to the arguments.
+
+ Forwards all arguments to self.style.apply(). The arguments should be a string,
+ to which this style is applied. This function returns the same string with
+ escape sequences added to apply this style.
+
+ If styling is globally disabled ('set style enabled off') then no escape sequences
+ will be addedded, the input string is returned."""
+ return self.style.apply(*args, **kwargs)
+
+ def __repr__(self):
+ """A string representation of SELF."""
+
+ def full_typename(obj):
+ module = type(obj).__module__
+ qualname = type(obj).__qualname__
+
+ if module is None or module == "builtins":
+ return qualname
+ else:
+ return module + "." + qualname
+
+ return "<" + full_typename(self) + " name='" + self._name + "'>"
diff --git a/gdb/testsuite/gdb.python/py-style-parameter-set.exp b/gdb/testsuite/gdb.python/py-style-parameter-set.exp
new file mode 100644
index 00000000000..9e05acb84cc
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-style-parameter-set.exp
@@ -0,0 +1,366 @@
+# 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.StyleParameterSet.
+
+load_lib gdb-python.exp
+
+require allow_python_tests
+
+# Create a regexp that can be used to match the output of a 'show style
+# NAME' command. HAS_INTENSITY is a boolean and indicates if style NAME has
+# an intensity attribute.
+proc gen_show_style_re { name has_intensity } {
+ set output \
+ [list \
+ "style ${name} background: The \"${name}\" style background color is: none" \
+ "style ${name} foreground: The \"${name}\" style foreground color is: none"]
+
+ if { $has_intensity } {
+ lappend output \
+ "style ${name} intensity: The \"${name}\" style display intensity is: normal"
+ }
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'set style NAME' command,
+# that is, a 'set' command that doesn't actually set an attribute of a
+# style, in this case GDB will print some useful help text. HAS_INTENSITY is
+# a boolean and indicates if style NAME has an intensity attribute or not.
+proc gen_set_style_re { name has_intensity } {
+ set output \
+ [list \
+ "List of \"set style $name\" subcommands:" \
+ "" \
+ "set style $name background -- Set the background color for this property\\." \
+ "set style $name foreground -- Set the foreground color for this property\\."]
+
+ if { $has_intensity } {
+ lappend output \
+ "set style $name intensity -- Set the display intensity for this property\\."
+ }
+
+ lappend output \
+ "" \
+ "Type \"help set style $name\" followed by subcommand name for full documentation\\." \
+ "Type \"apropos word\" to search for commands related to \"word\"\\." \
+ "Type \"apropos -v word\" for full documentation of commands related to \"word\"\\." \
+ "Command name abbreviations are allowed if unambiguous\\."
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'help show style NAME'
+# command. HAS_INTENSITY is a boolean and indicates if style NAME has an
+# intensity attribute or not. DOC is a regexp that matches the doc string
+# for style NAME.
+proc gen_help_show_style_re { name has_intensity doc } {
+ set output \
+ [list \
+ $doc \
+ "" \
+ "List of \"show style ${name}\" subcommands:" \
+ "" \
+ "show style $name background -- Show the background color for this property\\." \
+ "show style $name foreground -- Show the foreground color for this property\\."]
+ if { $has_intensity } {
+ lappend output \
+ "show style $name intensity -- Show the display intensity for this property\\."
+ }
+
+ lappend output \
+ "" \
+ "Type \"help show style $name\" followed by subcommand name for full documentation\\." \
+ "Type \"apropos word\" to search for commands related to \"word\"\\." \
+ "Type \"apropos -v word\" for full documentation of commands related to \"word\"\\." \
+ "Command name abbreviations are allowed if unambiguous\\."
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'help set style NAME'
+# command. HAS_INTENSITY is a boolean and indicates if style NAME has an
+# intensity attribute or not. DOC is a regexp that matches the doc string
+# for style NAME.
+proc gen_help_set_style_re { name has_intensity doc } {
+ return \
+ [multi_line \
+ $doc \
+ "" \
+ [gen_set_style_re $name $has_intensity]]
+}
+
+# Create styles with and without intensity. Use named and unnamed
+# argument passing, and vary the argument passing order. Create
+# styles with and without documentation.
+#
+# Confirm that the styles contain the expected sub-commands, and that
+# the documentation is as expected.
+proc_with_prefix test_basic_usage {} {
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"style-1\")" \
+ "create style-1"
+
+ gdb_test_no_output \
+ "python style_2 = gdb.StyleParameterSet(add_intensity=True, name=\"style-2\")" \
+ "create style-2"
+
+ gdb_test_no_output \
+ "python style_3 = gdb.StyleParameterSet(name=\"style-3\", doc=\"Style-3 display styling.\\nThis is a multi-line documentation\\nstring describing style-3.\")" \
+ "create style-3"
+
+ gdb_test_no_output \
+ "python style_4 = gdb.StyleParameterSet(\"style-4\", add_intensity=False)" \
+ "create style-4"
+
+ foreach style { style-1 style-2 style-3 } {
+ gdb_test "show style $style" \
+ [gen_show_style_re $style true]
+ gdb_test "set style $style" \
+ [gen_set_style_re $style true]
+ }
+
+ gdb_test "show style style-4" \
+ [gen_show_style_re "style-4" false]
+
+ foreach style { style-1 style-2 } {
+ gdb_test "help show style $style" \
+ [gen_help_show_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+
+
+ set out [gen_help_set_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+
+ gdb_test "help set style $style" \
+ [gen_help_set_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+ }
+
+ gdb_test "help show style style-3" \
+ [gen_help_show_style_re "style-3" true \
+ [multi_line \
+ "Style-3 display styling\\." \
+ "This is a multi-line documentation" \
+ "string describing style-3\\."]]
+
+ gdb_test "help show style style-4" \
+ [gen_help_show_style_re "style-4" false \
+ [multi_line \
+ "The style-4 display styling\\." \
+ "Configure style-4 colors and display intensity\\."]]
+
+ for { set i 1 } { $i < 5 } { incr i } {
+ gdb_test "python print(style_$i)" \
+ "<gdb.StyleParameterSet name='style-$i'>" \
+ "print repr of style_$i"
+ }
+
+ # There is no 'style-4 intensity' property.
+ gdb_test "show style style-4 foreground" \
+ "The \"style-4\" style foreground color is: none"
+ gdb_test "show style style-4 background" \
+ "The \"style-4\" style background color is: none"
+ gdb_test "show style style-4 intensity" \
+ "Undefined show style style-4 command: \"intensity\"\\. Try \"help show style style-4\"\\."
+
+ # There is a 'style-1 intensity' property.
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: none" \
+ "show style-1 foreground before changes"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: none" \
+ "show style-1 background before changes"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: normal" \
+ "show style-1 intensity before changes"
+
+ # Grab the gdb.Style objects from 'style-1'.
+ gdb_test_no_output "python s1 = style_1.style"
+ gdb_test_no_output "python s2 = style_1.value"
+
+ # Check both represent the same style.
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=none, bg=none, intensity=normal>" \
+ "print s1 style before changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=none, bg=none, intensity=normal>" \
+ "print s2 style before changes"
+
+ gdb_test_no_output \
+ "python s1.foreground=gdb.Color('red')" "set foreground"
+ gdb_test_no_output \
+ "python s1.background=gdb.Color('blue')" "set background"
+ gdb_test_no_output \
+ "python s1.intensity=gdb.INTENSITY_DIM" "set intensity"
+
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=red, bg=blue, intensity=dim>" \
+ "print s1 style after first set of changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=red, bg=blue, intensity=dim>" \
+ "print s2 style after first set of changes"
+
+ # Check the style properties have updated.
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: red" \
+ "show style-1 foreground after first set of changes"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: blue" \
+ "show style-1 background after first set of changes"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: dim" \
+ "show style-1 intensity after first set of changes"
+
+ # Change the style properties, check gdb.Style objects update.
+ gdb_test_no_output "set style style-1 foreground yellow"
+ gdb_test_no_output "set style style-1 background cyan"
+ gdb_test_no_output "set style style-1 intensity bold"
+
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=yellow, bg=cyan, intensity=bold>" \
+ "print s1 style after second set of changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=yellow, bg=cyan, intensity=bold>" \
+ "print s2 style after second set of changes"
+
+ # Assign a gdb.Style to set 'style-1'. First create some unnamed
+ # style objects that can be used.
+ gdb_test_no_output \
+ "python uns1 = gdb.Style(foreground=gdb.Color('white'), background=gdb.Color('black'), intensity=gdb.INTENSITY_BOLD)" \
+ "create uns1"
+ gdb_test_no_output \
+ "python uns2 = gdb.Style(foreground=gdb.Color('black'), background=gdb.Color('white'), intensity=gdb.INTENSITY_DIM)" \
+ "create uns2"
+
+ gdb_test_no_output "python style_1.value = uns1"
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: white" \
+ "show style-1 foreground after assigning uns1"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: black" \
+ "show style-1 background after assigning uns1"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: bold" \
+ "show style-1 intensity after assigning uns1"
+
+ gdb_test_no_output "python style_1.style = uns2"
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: black" \
+ "show style-1 foreground after assigning uns2"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: white" \
+ "show style-1 background after assigning uns2"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: dim" \
+ "show style-1 intensity after assigning uns2"
+
+ # Assign a style with an intensity that is not 'NORMAL' to a
+ # StyleParameterSet that doesn't have an intensity. The new
+ # intensity setting should be ignored.
+ gdb_test_no_output "python style_4.style = uns1"
+ gdb_test "show style style-4 foreground" \
+ "The \"style-4\" style foreground color is: white" \
+ "show style-4 foreground after assigning uns1"
+ gdb_test "show style style-4 background" \
+ "The \"style-4\" style background color is: black" \
+ "show style-4 background after assigning uns1"
+ gdb_test "show style style-4 intensity" \
+ "Undefined show style style-4 command: \"intensity\"\\. Try \"help show style style-4\"\\." \
+ "show style-4 intensity after assigning uns1"
+
+ gdb_test "python print(style_4.style)" \
+ "<gdb.Style name='style-4', fg=white, bg=black, intensity=normal>" \
+ "print string repr of style_4's style"
+}
+
+# Test creating a style prefix with gdb.ParameterPrefix, then adding
+# some styles within the new prefix. Change the style through the CLI
+# and confirm that the associated Python object updated as expected.
+proc_with_prefix test_style_prefix {} {
+ gdb_test_no_output \
+ "python gdb.ParameterPrefix(\"style my-style-group\", gdb.COMMAND_NONE)"
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"my-style-group style-1\")" \
+ "create style-1"
+ gdb_test_no_output \
+ "python style_2 = gdb.StyleParameterSet(\"my-style-group style-2\")" \
+ "create style-2"
+
+ gdb_test "python print(style_1.style)" \
+ "<gdb.Style name='my-style-group style-1', fg=none, bg=none, intensity=normal>" \
+ "print 'my-style-group style-1' style before changes"
+ gdb_test "python print(style_2.style)" \
+ "<gdb.Style name='my-style-group style-2', fg=none, bg=none, intensity=normal>" \
+ "print 'my-style-group style-2' style before changes"
+
+ gdb_test_no_output "set style my-style-group style-1 foreground red"
+ gdb_test_no_output "set style my-style-group style-1 background yellow"
+ gdb_test_no_output "set style my-style-group style-1 intensity bold"
+ gdb_test_no_output "set style my-style-group style-2 foreground black"
+ gdb_test_no_output "set style my-style-group style-2 background blue"
+ gdb_test_no_output "set style my-style-group style-2 intensity dim"
+
+ gdb_test "python print(style_1.style)" \
+ "<gdb.Style name='my-style-group style-1', fg=red, bg=yellow, intensity=bold>" \
+ "print 'my-style-group style-1' style after changes"
+ gdb_test "python print(style_2.style)" \
+ "<gdb.Style name='my-style-group style-2', fg=black, bg=blue, intensity=dim>" \
+ "print 'my-style-group style-2' style after changes"
+}
+
+# Test that gdb.StyleParameterSet.apply() works as expected.
+proc_with_prefix test_applying {} {
+ # Create a new StyleParameterSet, and adjust its settings.
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"style-1\")" \
+ "create style-1"
+ gdb_test_no_output \
+ "python uns1 = gdb.Style(foreground=gdb.Color('red'), background=gdb.Color('blue'), intensity=gdb.INTENSITY_BOLD)" \
+ "create uns1"
+ gdb_test_no_output "python style_1 = uns1"
+
+ # When styling is off (which it currently is), no escape sequences
+ # should be added.
+ gdb_test \
+ "python print(style_1.apply('xxx'))" "^xxx" \
+ "apply StyleParameterSet to a string when styling is off"
+
+ # When styling is on, we should see an escape sequence added.
+ gdb_test "with style enabled on -- python print(style_1.apply('xxx'))" \
+ "\033\\\[31;44;1;27mxxx\033\\\[m" \
+ "apply a style when styling is on"
+}
+
+# Start GDB.
+with_ansi_styling_terminal {
+ clean_restart
+}
+
+# Turn styling off so that the output of 'show style ...' isn't styled, this
+# makes it easier to match the output.
+gdb_test_no_output "set style enabled off"
+
+# Run the tests.
+test_basic_usage
+test_style_prefix
+test_applying
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* Re: [PATCHv2 1/2] gdb/python: add gdb.Style class
2025-06-06 13:38 ` [PATCHv2 1/2] gdb/python: add gdb.Style class Andrew Burgess
@ 2025-06-06 14:21 ` Eli Zaretskii
0 siblings, 0 replies; 61+ messages in thread
From: Eli Zaretskii @ 2025-06-06 14:21 UTC (permalink / raw)
To: Andrew Burgess; +Cc: gdb-patches
> From: Andrew Burgess <aburgess@redhat.com>
> Cc: Andrew Burgess <aburgess@redhat.com>
> Date: Fri, 6 Jun 2025 14:38:13 +0100
>
> 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.
> ---
> 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
Thanks, the documentation parts are okay.
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv3 0/2] Python Style API
2025-06-06 13:38 ` [PATCHv2 0/2] Python Style API Andrew Burgess
2025-06-06 13:38 ` [PATCHv2 1/2] gdb/python: add gdb.Style class Andrew Burgess
2025-06-06 13:38 ` [PATCHv2 2/2] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
@ 2025-06-07 10:44 ` Andrew Burgess
2025-06-07 10:44 ` [PATCHv3 1/2] gdb/python: add gdb.Style class Andrew Burgess
` (3 more replies)
2 siblings, 4 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-06-07 10:44 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
In v3:
- Another test issue. This time, the error message given when
parsing an argument of the wrong type changes with Python version.
Relax the regexp to accept any 'TypeError' message.
In v2:
- Fixed an issue with the py-style-parameter-set.exp test; GDB was
not started in a terminal with styling support in one case, so
tests would fail depending on how the testsuite was being run.
---
After the gdb.Color support was added to the Python API I wondered if
this would be enough to easily add styling to custom Python commands.
But I don't think it is, there's still lots that would need to be
handled in user code, for example, the intensity setting. And having
styles that track named styles (e.g. if I want to apply the 'filename'
style to some text from Python).
So this series builds on top of the gdb.Color API to provide a new
gdb.Style API which, I hope, makes it easy to start writing Python
commands that can use GDB's styles.
Here's an example of a simple custome command that uses styles:
import os
class user_home_cmd(gdb.Command):
def __init__(self):
super().__init__("user-home", gdb.COMMAND_USER)
self._style = gdb.Style('filename')
def invoke(self, args, from_tty):
print("The filename is %s."
% (self._style.apply(os.environ['HOME'])))
user_home_cmd()
The new 'user-home' command prints the current users home directory
using 'filename' style. Changing the 'filename' style and re-running
the command will track the style changes.
Thanks,
Andrew
---
Andrew Burgess (2):
gdb/python: add gdb.Style class
gdb/python: new class gdb.StyleParameterSet
gdb/Makefile.in | 1 +
gdb/NEWS | 11 +
gdb/doc/python.texi | 253 ++++++
gdb/python/lib/gdb/__init__.py | 209 +++++
gdb/python/py-style.c | 797 ++++++++++++++++++
.../gdb.python/py-style-parameter-set.exp | 366 ++++++++
gdb/testsuite/gdb.python/py-style.exp | 342 ++++++++
gdb/ui-style.h | 6 +
8 files changed, 1985 insertions(+)
create mode 100644 gdb/python/py-style.c
create mode 100644 gdb/testsuite/gdb.python/py-style-parameter-set.exp
create mode 100644 gdb/testsuite/gdb.python/py-style.exp
base-commit: 41cb00ce25900aadbf089f80bb2830494bf2a780
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv3 1/2] gdb/python: add gdb.Style class
2025-06-07 10:44 ` [PATCHv3 0/2] Python Style API Andrew Burgess
@ 2025-06-07 10:44 ` Andrew Burgess
2025-06-07 10:44 ` [PATCHv3 2/2] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
` (2 subsequent siblings)
3 siblings, 0 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-06-07 10:44 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess, Eli Zaretskii
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
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv3 2/2] gdb/python: new class gdb.StyleParameterSet
2025-06-07 10:44 ` [PATCHv3 0/2] Python Style API Andrew Burgess
2025-06-07 10:44 ` [PATCHv3 1/2] gdb/python: add gdb.Style class Andrew Burgess
@ 2025-06-07 10:44 ` Andrew Burgess
2025-06-09 15:54 ` [PATCHv3 0/2] Python Style API Andrew Burgess
2025-06-18 19:30 ` [PATCHv4 0/4] " Andrew Burgess
3 siblings, 0 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-06-07 10:44 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
Add a new helper class gdb.StyleParameterSet. This new class can be
used to simplify creation of new style parameter sets. A style
parameter set is the 'foreground', 'background', and (optionally), the
'intensity' settings, all grouped under a single prefix command.
And example usage is:
(gdb) python s = gdb.StyleParameterSet("my-style")
(gdb) show style my-style
style my-style background: The "my-style" style background color is: none
style my-style foreground: The "my-style" style foreground color is: none
style my-style intensity: The "my-style" style display intensity is: normal
(gdb)
Having created a gdb.StyleParameterSet, the object itself can be used
to access a named style corresponding to the setting group, like this:
(gdb) python print(s.style)
<gdb.Style name='my-style', fg=none, bg=none, intensity=normal>
(gdb)
Of course, having access to the gdb.Style makes it easy to change the
settings, or the settings can be adjusted via the normal CLI 'set'
commands.
As gdb.StyleParameterSet manages a set of parameters, and the
gdb.Parameter class uses Parameter.value as the attribute to read the
parameter's value, there is also StyleParameterSet.value, but this is
just an alias for StyleParameterSet.style, that is, it allows the
gdb.Style object to be read and written too.
It is worth noting that this class only creates a single level of
prefix command. As an example GDB has style 'disassembler mnemonic',
where the 'disassembler' part is a group of related styles. If a user
wanted to create:
style
my-style-group
style-1
style-2
style-3
Where each of 'style-1', 'style-2', and 'style-3' will have the full
set of 'foreground', 'background', and 'intensity', then the
gdb.StyleParameterSet can be used to create the 'style-N' part, but
the user will have to create the 'my-style-group' prefix themselves,
possibly using gdb.ParameterPrefix, e.g.:
gdb.ParameterPrefix("style my-style-group", gdb.COMMAND_NONE)
gdb.StyleParameterSet("my-style-group style-1")
gdb.StyleParameterSet("my-style-group style-2")
gdb.StyleParameterSet("my-style-group style-3")
---
gdb/NEWS | 4 +
gdb/doc/python.texi | 115 +++++-
gdb/python/lib/gdb/__init__.py | 209 ++++++++++
.../gdb.python/py-style-parameter-set.exp | 366 ++++++++++++++++++
gdb/testsuite/gdb.python/py-style.exp | 7 +-
5 files changed, 698 insertions(+), 3 deletions(-)
create mode 100644 gdb/testsuite/gdb.python/py-style-parameter-set.exp
diff --git a/gdb/NEWS b/gdb/NEWS
index e0b1efd6787..90185fe4579 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -138,6 +138,10 @@ info threads [-gid] [-stopped] [-running] [ID]...
gdb.INTENSITY_DIM for use with gdb.Style when representing
intensities.
+ ** New gdb.StyleParameterSet for creating custom style settings.
+ Use gdb.StyleParameterSet(NAME) to create 'set style NAME ...'
+ and 'show style NAME ...' parameters.
+
** 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 db50dc5f635..04367c88e4b 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -5282,6 +5282,7 @@ Parameters In Python
feature-2}, then the @kbd{plugin-name} would need to be a prefix
command (@pxref{CLI Commands In Python}).
+@anchor{gdb.ParameterPrefix}
However, when creating parameters, you will almost always need to
create two prefix commands, one as a @kbd{set} sub-command, and one as
a @kbd{show} sub-command. @value{GDBN} provides the
@@ -5368,6 +5369,117 @@ Parameters In Python
ExampleParam("plugin-name feature-2")
@end smallexample
+@anchor{Creating Style Parameters}
+The helper class @code{gdb.StyleParameterSet} exists to make it easier
+to create new styles. @value{GDBN} places style settings under
+@samp{show style @dots{}} and @samp{set style @dots{}}, an example of a style
+is @samp{filename}. Each style is really a prefix command (@pxref{CLI
+Commands In Python}), with sub-commands @samp{foreground},
+@samp{background}, and optionally, @samp{intensity}.
+
+It is simple enough to create a new style using two @code{gdb.Command}
+objects for the prefix commands (one for @samp{set}, and one for
+@samp{show}), and three @code{gdb.Parameter} objects, one each for the
+@samp{foreground}, @samp{background}, and @samp{intensity}. You would
+also want to take care to craft the help text so that the new style
+behaves the same as the existing styles.
+
+Or, you can use the @code{gdb.StyleParameterSet} class, which takes
+care of all this, as the following example shows:
+
+@smallexample
+@group
+(@value{GDBP}) python s = gdb.StyleParameterSet("my-style")
+(@value{GDBP}) show style my-style
+style my-style background: The "my-style" style background color is: none
+style my-style foreground: The "my-style" style foreground color is: none
+style my-style intensity: The "my-style" style display intensity is: normal
+(@value{GDBP})
+@end group
+@end smallexample
+
+You might also want to group a number of styles within a new prefix,
+similar to how @value{GDBN} groups disassembler related styles within
+the @samp{style disassembler} prefix. This can be done using
+@code{gdb.ParameterPrefix} (@pxref{gdb.ParameterPrefix}), as in this
+example:
+
+@smallexample
+@group
+(@value{GDBP}) python gdb.ParameterPrefix("style my-group", gdb.COMMAND_NONE)
+(@value{GDBP}) python s_a = gdb.StyleParameterSet("my-group aaa")
+(@value{GDBP}) python s_b = gdb.StyleParameterSet("my-group bbb")
+(@value{GDBP}) show style my-group
+style my-group aaa background: The "my-group aaa" style background color is: none
+style my-group aaa foreground: The "my-group aaa" style foreground color is: none
+style my-group aaa intensity: The "my-group aaa" style display intensity is: normal
+style my-group bbb background: The "my-group bbb" style background color is: none
+style my-group bbb foreground: The "my-group bbb" style foreground color is: none
+style my-group bbb intensity: The "my-group bbb" style display intensity is: normal
+(@value{GDBP})
+@end group
+@end smallexample
+
+The @code{gdb.StyleParameterSet} class has the following methods and
+attributes:
+
+@defun StyleParameterSet.__init__(name, @w{add_intensity=@code{True}}, @w{doc=@code{None}})
+Create a new style group based on @var{name}, which is a string. For
+example if @var{name} is @samp{my-style}, then @value{GDBN} will
+create the prefix commands @samp{set style my-style} and @samp{show
+style my-style}. Within these prefix commands will be
+@samp{foreground}, @samp{background}, and @samp{intensity} parameters
+with the appropriate types.
+
+It is also possible for @var{name} to consist of multiple words, so
+long as each prefix command (except the last one) already exists. For
+example, it is valid to use a @var{name} value of @samp{disassembler
+my-style}, as the @samp{disassembler} prefix command already exists.
+@value{GDBN} would then create @samp{set style disassembler my-style}
+and @samp{show style disassembler my-style}, and within the
+@samp{my-style} prefixes will be the @samp{foreground},
+@samp{background}, and @samp{intensity} parameters with the
+appropriate types.
+
+Every style requires a @samp{foreground} and @samp{background}, but
+not every style needs an @samp{intensity}. If @var{add_intensity} is
+@code{True} (the default), then the @samp{intensity} parameter will be
+created. If @var{add_intensity} is @code{False}, then the
+@samp{intensity} parameter will not be created.
+
+If the @samp{intensity} parameter is not created, then the
+@code{gdb.Style} (@pxref{Styles In Python}) created from this
+@code{gdb.StyleParameterSet} will have @code{gdb.INTENSITY_NORMAL}.
+
+The @var{doc} should be a string which will be used as the help text
+for the @var{name} prefix command. This text is used as the
+@code{Command.__doc__} value for the @code{gdb.Command} object that is
+the prefix command object (@pxref{CLI Commands In Python}). If
+@var{doc} is @code{None} (the default) then a basic default help text
+is used.
+@end defun
+
+@defun StyleParameterSet.apply(string)
+Equivalent to @code{StyleParameterSet.style.apply(string)}. Returns a
+copy of @var{string} with escape sequences added to the start and end.
+The escape sequence at the start applies this style, and the escape
+sequence at the end restores the terminal default.
+
+If styling is disabled (i.e.@: @samp{set style enabled off}), then no
+escape sequences are added and this method returns a copy of
+@var{string}.
+@end defun
+
+@defvar StyleParameterSet.style
+This read/write attribute holds a @code{gdb.Style} object
+(@pxref{Styles In Python}), that is a named style associated with this
+style parameter group.
+@end defvar
+
+@defvar StyleParameterSet.value
+This is an alias for @code{StyleParameterSet.style}, see above.
+@end defvar
+
@node Functions In Python
@subsubsection Writing new convenience functions
@@ -7264,7 +7376,8 @@ Styles In Python
@value{GDBN} has many styles builtin (@pxref{Output Styling}), and
style objects can be created that apply these builtin styles to Python
-output.
+output. It is also possible to create new styles which can be used to
+style Python output (@pxref{Creating Style Parameters}).
The style class is called @code{gdb.Style}, and has the following
methods and attributes:
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index cedd897ab0f..f2f57280007 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -515,3 +515,212 @@ class ParameterPrefix:
def dont_repeat(self):
if self.active_prefix is not None:
self.active_prefix.dont_repeat()
+
+
+class StyleParameterSet:
+ """Create new style parameters.
+
+ A style parameter is a set of parameters that start with 'set style ...'
+ and 'show style ...'. For example 'filename' is a style parameter, and
+ 'disassembler symbol' is another style parameter.
+
+ The name of the style parameter is really a prefix command. Under this
+ we must have two commands 'foreground' and 'background', which are color
+ parameters. A third, optional command 'intensity', is an enum with
+ values 'normal', 'bold', and 'dim'.
+
+ A StyleParameterSet is initialised with a name, e.g. 'filename' or
+ 'disassembler symbol'. The StyleParameterSet creates the prefix
+ commands in the 'set style' and 'show style' name space, and then adds
+ the 'foreground', 'background', and optionally, the 'intensity'
+ commands.
+
+ If you want a whole new style group, similar to 'disassembler',
+ then you need to add this yourself first, then StyleParameterSet
+ can be used to create styles within the new prefix group.
+
+ The 'value' attribute on this object can be used to get and set a
+ gdb.Style object which controls all aspects of this style.
+
+ For readability, the alias 'style' is the same as 'value'.
+ """
+
+ def __init__(self, name, add_intensity=True, doc=None):
+ # The STYLE_NAME is something like 'filename' is 'set style
+ # filename ...', and PARAM_NAME is one of 'foreground',
+ # 'background', or 'intensity'. The DESC_TEXT is the long
+ # form used in help text, like 'foreground color' or 'display
+ # intensity'. The DEFAULT_VALUE is used to set the SELF.value
+ # attribute. And PARAM_TYPE is a gdb.PARAM_* constant. The
+ # ARGS is used for gdb.PARAM_ENUM, which ARGS should be the
+ # enum value list.
+ class style_parameter(Parameter):
+ def __init__(
+ self,
+ style_name,
+ parent_obj,
+ param_name,
+ desc_text,
+ default_value,
+ param_type,
+ *args
+ ):
+ # Setup documentation must be done before calling
+ # parent's __init__ method, as the __init__ reads (and
+ # copies) these values.
+ self.show_doc = "Show the " + desc_text + " for this property."
+ self.set_doc = "Set the " + desc_text + " for this property."
+ self.__doc__ = ""
+
+ # Call the parent's __init__ method to actually create
+ # the parameter.
+ super().__init__(
+ "style " + style_name + " " + param_name,
+ COMMAND_NONE,
+ param_type,
+ *args
+ )
+
+ # Store information we need in other methods.
+ self._style_name = style_name
+ self._desc_text = desc_text
+ self._parent = parent_obj
+
+ # Finally, setup the default value.
+ self.value = default_value
+
+ # Return the 'show style <style-name> <attribute>' string,
+ # which has styling applied.
+ def get_show_string(self, value):
+ s = self._parent.style
+ return (
+ "The "
+ + s.apply('"' + self._style_name + '" style')
+ + " "
+ + self._desc_text
+ + " is: "
+ + value
+ )
+
+ class style_foreground_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "foreground",
+ "foreground color",
+ Color(),
+ PARAM_COLOR,
+ )
+
+ class style_background_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "background",
+ "background color",
+ Color(),
+ PARAM_COLOR,
+ )
+
+ class style_intensity_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "intensity",
+ "display intensity",
+ "normal",
+ PARAM_ENUM,
+ ["normal", "bold", "dim"],
+ )
+
+ if doc is None:
+ doc = (
+ "The "
+ + name
+ + " display styling.\nConfigure "
+ + name
+ + " colors and display intensity."
+ )
+
+ ParameterPrefix("style " + name, COMMAND_NONE, doc)
+ self._foreground = style_foreground_parameter(name, self)
+ self._background = style_background_parameter(name, self)
+ if add_intensity:
+ self._intensity = style_intensity_parameter(name, self)
+ self._name = name
+ self._style = None
+
+ @property
+ def value(self):
+ """Return the gdb.Style object for this parameter set."""
+ if self._style is None:
+ self._style = Style(self._name)
+ return self._style
+
+ @property
+ def style(self):
+ """Return the gdb.Style object for this parameter set.
+
+ This is an alias for self.value."""
+ return self.value
+
+ @value.setter
+ def value(self, new_value):
+ """Set this parameter set to NEW_VALUE, a gdb.Style object.
+
+ The attributes of NEW_VALUE are used to update the current settings
+ of this parameter set. If this parameter set was created without
+ an intensity setting, then the intensity of NEW_VALUE is ignored."""
+ if not isinstance(new_value, Style):
+ raise TypeError("value must be gdb.Style, not %s" % type(new_value))
+ self._foreground.value = new_value.foreground
+ self._background.value = new_value.background
+ if hasattr(self, "_intensity"):
+ intensity_value = new_value.intensity
+ if intensity_value == INTENSITY_BOLD:
+ intensity_string = "bold"
+ elif intensity_value == INTENSITY_DIM:
+ intensity_string = "dim"
+ elif intensity_value == INTENSITY_NORMAL:
+ intensity_string = "normal"
+ else:
+ raise ValueError(
+ "unknown intensity value %d from Style" % intensity_value
+ )
+
+ self._intensity.value = intensity_string
+
+ @style.setter
+ def style(self, new_value):
+ """Set this parameter set to NEW_VALUE, a gdb.Style object.
+
+ This is an alias for self.value."""
+ self.value = new_value
+
+ def apply(self, *args, **kwargs):
+ """Apply this style to the arguments.
+
+ Forwards all arguments to self.style.apply(). The arguments should be a string,
+ to which this style is applied. This function returns the same string with
+ escape sequences added to apply this style.
+
+ If styling is globally disabled ('set style enabled off') then no escape sequences
+ will be addedded, the input string is returned."""
+ return self.style.apply(*args, **kwargs)
+
+ def __repr__(self):
+ """A string representation of SELF."""
+
+ def full_typename(obj):
+ module = type(obj).__module__
+ qualname = type(obj).__qualname__
+
+ if module is None or module == "builtins":
+ return qualname
+ else:
+ return module + "." + qualname
+
+ return "<" + full_typename(self) + " name='" + self._name + "'>"
diff --git a/gdb/testsuite/gdb.python/py-style-parameter-set.exp b/gdb/testsuite/gdb.python/py-style-parameter-set.exp
new file mode 100644
index 00000000000..9e05acb84cc
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-style-parameter-set.exp
@@ -0,0 +1,366 @@
+# 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.StyleParameterSet.
+
+load_lib gdb-python.exp
+
+require allow_python_tests
+
+# Create a regexp that can be used to match the output of a 'show style
+# NAME' command. HAS_INTENSITY is a boolean and indicates if style NAME has
+# an intensity attribute.
+proc gen_show_style_re { name has_intensity } {
+ set output \
+ [list \
+ "style ${name} background: The \"${name}\" style background color is: none" \
+ "style ${name} foreground: The \"${name}\" style foreground color is: none"]
+
+ if { $has_intensity } {
+ lappend output \
+ "style ${name} intensity: The \"${name}\" style display intensity is: normal"
+ }
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'set style NAME' command,
+# that is, a 'set' command that doesn't actually set an attribute of a
+# style, in this case GDB will print some useful help text. HAS_INTENSITY is
+# a boolean and indicates if style NAME has an intensity attribute or not.
+proc gen_set_style_re { name has_intensity } {
+ set output \
+ [list \
+ "List of \"set style $name\" subcommands:" \
+ "" \
+ "set style $name background -- Set the background color for this property\\." \
+ "set style $name foreground -- Set the foreground color for this property\\."]
+
+ if { $has_intensity } {
+ lappend output \
+ "set style $name intensity -- Set the display intensity for this property\\."
+ }
+
+ lappend output \
+ "" \
+ "Type \"help set style $name\" followed by subcommand name for full documentation\\." \
+ "Type \"apropos word\" to search for commands related to \"word\"\\." \
+ "Type \"apropos -v word\" for full documentation of commands related to \"word\"\\." \
+ "Command name abbreviations are allowed if unambiguous\\."
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'help show style NAME'
+# command. HAS_INTENSITY is a boolean and indicates if style NAME has an
+# intensity attribute or not. DOC is a regexp that matches the doc string
+# for style NAME.
+proc gen_help_show_style_re { name has_intensity doc } {
+ set output \
+ [list \
+ $doc \
+ "" \
+ "List of \"show style ${name}\" subcommands:" \
+ "" \
+ "show style $name background -- Show the background color for this property\\." \
+ "show style $name foreground -- Show the foreground color for this property\\."]
+ if { $has_intensity } {
+ lappend output \
+ "show style $name intensity -- Show the display intensity for this property\\."
+ }
+
+ lappend output \
+ "" \
+ "Type \"help show style $name\" followed by subcommand name for full documentation\\." \
+ "Type \"apropos word\" to search for commands related to \"word\"\\." \
+ "Type \"apropos -v word\" for full documentation of commands related to \"word\"\\." \
+ "Command name abbreviations are allowed if unambiguous\\."
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'help set style NAME'
+# command. HAS_INTENSITY is a boolean and indicates if style NAME has an
+# intensity attribute or not. DOC is a regexp that matches the doc string
+# for style NAME.
+proc gen_help_set_style_re { name has_intensity doc } {
+ return \
+ [multi_line \
+ $doc \
+ "" \
+ [gen_set_style_re $name $has_intensity]]
+}
+
+# Create styles with and without intensity. Use named and unnamed
+# argument passing, and vary the argument passing order. Create
+# styles with and without documentation.
+#
+# Confirm that the styles contain the expected sub-commands, and that
+# the documentation is as expected.
+proc_with_prefix test_basic_usage {} {
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"style-1\")" \
+ "create style-1"
+
+ gdb_test_no_output \
+ "python style_2 = gdb.StyleParameterSet(add_intensity=True, name=\"style-2\")" \
+ "create style-2"
+
+ gdb_test_no_output \
+ "python style_3 = gdb.StyleParameterSet(name=\"style-3\", doc=\"Style-3 display styling.\\nThis is a multi-line documentation\\nstring describing style-3.\")" \
+ "create style-3"
+
+ gdb_test_no_output \
+ "python style_4 = gdb.StyleParameterSet(\"style-4\", add_intensity=False)" \
+ "create style-4"
+
+ foreach style { style-1 style-2 style-3 } {
+ gdb_test "show style $style" \
+ [gen_show_style_re $style true]
+ gdb_test "set style $style" \
+ [gen_set_style_re $style true]
+ }
+
+ gdb_test "show style style-4" \
+ [gen_show_style_re "style-4" false]
+
+ foreach style { style-1 style-2 } {
+ gdb_test "help show style $style" \
+ [gen_help_show_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+
+
+ set out [gen_help_set_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+
+ gdb_test "help set style $style" \
+ [gen_help_set_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+ }
+
+ gdb_test "help show style style-3" \
+ [gen_help_show_style_re "style-3" true \
+ [multi_line \
+ "Style-3 display styling\\." \
+ "This is a multi-line documentation" \
+ "string describing style-3\\."]]
+
+ gdb_test "help show style style-4" \
+ [gen_help_show_style_re "style-4" false \
+ [multi_line \
+ "The style-4 display styling\\." \
+ "Configure style-4 colors and display intensity\\."]]
+
+ for { set i 1 } { $i < 5 } { incr i } {
+ gdb_test "python print(style_$i)" \
+ "<gdb.StyleParameterSet name='style-$i'>" \
+ "print repr of style_$i"
+ }
+
+ # There is no 'style-4 intensity' property.
+ gdb_test "show style style-4 foreground" \
+ "The \"style-4\" style foreground color is: none"
+ gdb_test "show style style-4 background" \
+ "The \"style-4\" style background color is: none"
+ gdb_test "show style style-4 intensity" \
+ "Undefined show style style-4 command: \"intensity\"\\. Try \"help show style style-4\"\\."
+
+ # There is a 'style-1 intensity' property.
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: none" \
+ "show style-1 foreground before changes"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: none" \
+ "show style-1 background before changes"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: normal" \
+ "show style-1 intensity before changes"
+
+ # Grab the gdb.Style objects from 'style-1'.
+ gdb_test_no_output "python s1 = style_1.style"
+ gdb_test_no_output "python s2 = style_1.value"
+
+ # Check both represent the same style.
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=none, bg=none, intensity=normal>" \
+ "print s1 style before changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=none, bg=none, intensity=normal>" \
+ "print s2 style before changes"
+
+ gdb_test_no_output \
+ "python s1.foreground=gdb.Color('red')" "set foreground"
+ gdb_test_no_output \
+ "python s1.background=gdb.Color('blue')" "set background"
+ gdb_test_no_output \
+ "python s1.intensity=gdb.INTENSITY_DIM" "set intensity"
+
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=red, bg=blue, intensity=dim>" \
+ "print s1 style after first set of changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=red, bg=blue, intensity=dim>" \
+ "print s2 style after first set of changes"
+
+ # Check the style properties have updated.
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: red" \
+ "show style-1 foreground after first set of changes"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: blue" \
+ "show style-1 background after first set of changes"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: dim" \
+ "show style-1 intensity after first set of changes"
+
+ # Change the style properties, check gdb.Style objects update.
+ gdb_test_no_output "set style style-1 foreground yellow"
+ gdb_test_no_output "set style style-1 background cyan"
+ gdb_test_no_output "set style style-1 intensity bold"
+
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=yellow, bg=cyan, intensity=bold>" \
+ "print s1 style after second set of changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=yellow, bg=cyan, intensity=bold>" \
+ "print s2 style after second set of changes"
+
+ # Assign a gdb.Style to set 'style-1'. First create some unnamed
+ # style objects that can be used.
+ gdb_test_no_output \
+ "python uns1 = gdb.Style(foreground=gdb.Color('white'), background=gdb.Color('black'), intensity=gdb.INTENSITY_BOLD)" \
+ "create uns1"
+ gdb_test_no_output \
+ "python uns2 = gdb.Style(foreground=gdb.Color('black'), background=gdb.Color('white'), intensity=gdb.INTENSITY_DIM)" \
+ "create uns2"
+
+ gdb_test_no_output "python style_1.value = uns1"
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: white" \
+ "show style-1 foreground after assigning uns1"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: black" \
+ "show style-1 background after assigning uns1"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: bold" \
+ "show style-1 intensity after assigning uns1"
+
+ gdb_test_no_output "python style_1.style = uns2"
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: black" \
+ "show style-1 foreground after assigning uns2"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: white" \
+ "show style-1 background after assigning uns2"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: dim" \
+ "show style-1 intensity after assigning uns2"
+
+ # Assign a style with an intensity that is not 'NORMAL' to a
+ # StyleParameterSet that doesn't have an intensity. The new
+ # intensity setting should be ignored.
+ gdb_test_no_output "python style_4.style = uns1"
+ gdb_test "show style style-4 foreground" \
+ "The \"style-4\" style foreground color is: white" \
+ "show style-4 foreground after assigning uns1"
+ gdb_test "show style style-4 background" \
+ "The \"style-4\" style background color is: black" \
+ "show style-4 background after assigning uns1"
+ gdb_test "show style style-4 intensity" \
+ "Undefined show style style-4 command: \"intensity\"\\. Try \"help show style style-4\"\\." \
+ "show style-4 intensity after assigning uns1"
+
+ gdb_test "python print(style_4.style)" \
+ "<gdb.Style name='style-4', fg=white, bg=black, intensity=normal>" \
+ "print string repr of style_4's style"
+}
+
+# Test creating a style prefix with gdb.ParameterPrefix, then adding
+# some styles within the new prefix. Change the style through the CLI
+# and confirm that the associated Python object updated as expected.
+proc_with_prefix test_style_prefix {} {
+ gdb_test_no_output \
+ "python gdb.ParameterPrefix(\"style my-style-group\", gdb.COMMAND_NONE)"
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"my-style-group style-1\")" \
+ "create style-1"
+ gdb_test_no_output \
+ "python style_2 = gdb.StyleParameterSet(\"my-style-group style-2\")" \
+ "create style-2"
+
+ gdb_test "python print(style_1.style)" \
+ "<gdb.Style name='my-style-group style-1', fg=none, bg=none, intensity=normal>" \
+ "print 'my-style-group style-1' style before changes"
+ gdb_test "python print(style_2.style)" \
+ "<gdb.Style name='my-style-group style-2', fg=none, bg=none, intensity=normal>" \
+ "print 'my-style-group style-2' style before changes"
+
+ gdb_test_no_output "set style my-style-group style-1 foreground red"
+ gdb_test_no_output "set style my-style-group style-1 background yellow"
+ gdb_test_no_output "set style my-style-group style-1 intensity bold"
+ gdb_test_no_output "set style my-style-group style-2 foreground black"
+ gdb_test_no_output "set style my-style-group style-2 background blue"
+ gdb_test_no_output "set style my-style-group style-2 intensity dim"
+
+ gdb_test "python print(style_1.style)" \
+ "<gdb.Style name='my-style-group style-1', fg=red, bg=yellow, intensity=bold>" \
+ "print 'my-style-group style-1' style after changes"
+ gdb_test "python print(style_2.style)" \
+ "<gdb.Style name='my-style-group style-2', fg=black, bg=blue, intensity=dim>" \
+ "print 'my-style-group style-2' style after changes"
+}
+
+# Test that gdb.StyleParameterSet.apply() works as expected.
+proc_with_prefix test_applying {} {
+ # Create a new StyleParameterSet, and adjust its settings.
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"style-1\")" \
+ "create style-1"
+ gdb_test_no_output \
+ "python uns1 = gdb.Style(foreground=gdb.Color('red'), background=gdb.Color('blue'), intensity=gdb.INTENSITY_BOLD)" \
+ "create uns1"
+ gdb_test_no_output "python style_1 = uns1"
+
+ # When styling is off (which it currently is), no escape sequences
+ # should be added.
+ gdb_test \
+ "python print(style_1.apply('xxx'))" "^xxx" \
+ "apply StyleParameterSet to a string when styling is off"
+
+ # When styling is on, we should see an escape sequence added.
+ gdb_test "with style enabled on -- python print(style_1.apply('xxx'))" \
+ "\033\\\[31;44;1;27mxxx\033\\\[m" \
+ "apply a style when styling is on"
+}
+
+# Start GDB.
+with_ansi_styling_terminal {
+ clean_restart
+}
+
+# Turn styling off so that the output of 'show style ...' isn't styled, this
+# makes it easier to match the output.
+gdb_test_no_output "set style enabled off"
+
+# Run the tests.
+test_basic_usage
+test_style_prefix
+test_applying
diff --git a/gdb/testsuite/gdb.python/py-style.exp b/gdb/testsuite/gdb.python/py-style.exp
index b7240a50b3a..020d0e0319b 100644
--- a/gdb/testsuite/gdb.python/py-style.exp
+++ b/gdb/testsuite/gdb.python/py-style.exp
@@ -203,10 +203,13 @@ 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'>: an integer is required \\(got type NoneType\\)" \
- "Error occurred in Python: an integer is required \\(got type NoneType\\)"] \
+ "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())" \
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* Re: [PATCHv3 0/2] Python Style API
2025-06-07 10:44 ` [PATCHv3 0/2] Python Style API Andrew Burgess
2025-06-07 10:44 ` [PATCHv3 1/2] gdb/python: add gdb.Style class Andrew Burgess
2025-06-07 10:44 ` [PATCHv3 2/2] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
@ 2025-06-09 15:54 ` Andrew Burgess
2025-06-18 19:30 ` [PATCHv4 0/4] " Andrew Burgess
3 siblings, 0 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-06-09 15:54 UTC (permalink / raw)
To: gdb-patches
Andrew Burgess <aburgess@redhat.com> writes:
> In v3:
>
> - Another test issue. This time, the error message given when
> parsing an argument of the wrong type changes with Python version.
> Relax the regexp to accept any 'TypeError' message.
I managed to add the "fix" for this issue into patch #2 instead of patch
#1. When looking at patch #2 you'll notice a random change to
gdb.python/py-style.exp, that's the fix in question.
Locally I've moved this fix into patch #1. I'll hold off posting a v4
for now.
Thanks,
Andrew
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv4 0/4] Python Style API
2025-06-07 10:44 ` [PATCHv3 0/2] Python Style API Andrew Burgess
` (2 preceding siblings ...)
2025-06-09 15:54 ` [PATCHv3 0/2] Python Style API Andrew Burgess
@ 2025-06-18 19:30 ` Andrew Burgess
2025-06-18 19:30 ` [PATCHv4 1/4] gdb: allow gdb.Color to work correctly with pagination Andrew Burgess
` (4 more replies)
3 siblings, 5 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-06-18 19:30 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
After the gdb.Color support was added to the Python API I wondered if
this would be enough to easily add styling to custom Python commands.
But I don't think it is, there's still lots that would need to be
handled in user code, for example, the intensity setting. And having
styles that track named styles (e.g. if I want to apply the 'filename'
style to some text from Python).
So this series builds on top of the gdb.Color API to provide a new
gdb.Style API which, I hope, makes it easy to start writing Python
commands that can use GDB's styles.
Here's an example of a simple custome command that uses styles:
import os
class user_home_cmd(gdb.Command):
def __init__(self):
super().__init__("user-home", gdb.COMMAND_USER)
self._style = gdb.Style('filename')
def invoke(self, args, from_tty):
print("The filename is %s."
% (self._style.apply(os.environ['HOME'])))
user_home_cmd()
The new 'user-home' command prints the current users home directory
using 'filename' style. Changing the 'filename' style and re-running
the command will track the style changes.
In v2:
- Fixed an issue with the py-style-parameter-set.exp test; GDB was
not started in a terminal with styling support in one case, so
tests would fail depending on how the testsuite was being run.
In v3:
- Another test issue. This time, the error message given when
parsing an argument of the wrong type changes with Python version.
Relax the regexp to accept any 'TypeError' message.
In v4:
- Two new patches. First patch extends pager_file so that
gdb.Color, and gdb.Style when it's added can work well with GDB's
pager. Last patch extends the gdb.write() Python function to also
support styling.
- I've renamed some of the classes in python/py-style.c, nothing
major, I replaced 'stylepy' with 'style' in some names. I think
the new names are clearer, and better match the rest of GDB's
Python API code.
- Unfortunately, the new pager_file changes mean that this patch
series now depends on this other patch:
https://inbox.sourceware.org/gdb-patches/444008aeae2bb3c68cf868fa317374b3d7973860.1750197766.git.aburgess@redhat.com
Without that patch there will be some failures in
gdb.python/py-color-pagination.exp. I'm not proposing to merge
this until that other patch, or something like it, is merged. But
this series could be reviewed independently.
Thanks,
Andrew
---
Andrew Burgess (4):
gdb: allow gdb.Color to work correctly with pagination
gdb/python: add gdb.Style class
gdb/python: new class gdb.StyleParameterSet
gdb: extend gdb.write to support styled output
gdb/Makefile.in | 1 +
gdb/NEWS | 14 +
gdb/doc/python.texi | 262 +++++-
gdb/python/lib/gdb/__init__.py | 209 +++++
gdb/python/py-style.c | 824 ++++++++++++++++++
gdb/python/python-internal.h | 15 +
gdb/python/python.c | 46 +-
.../gdb.python/py-color-pagination.exp | 166 ++++
.../gdb.python/py-color-pagination.py | 82 ++
.../gdb.python/py-style-parameter-set.exp | 366 ++++++++
gdb/testsuite/gdb.python/py-style.exp | 371 ++++++++
gdb/ui-style.h | 6 +
gdb/utils.c | 21 +-
13 files changed, 2366 insertions(+), 17 deletions(-)
create mode 100644 gdb/python/py-style.c
create mode 100644 gdb/testsuite/gdb.python/py-color-pagination.exp
create mode 100644 gdb/testsuite/gdb.python/py-color-pagination.py
create mode 100644 gdb/testsuite/gdb.python/py-style-parameter-set.exp
create mode 100644 gdb/testsuite/gdb.python/py-style.exp
base-commit: 56443763a8304ee7bec3b773f98104c754c5d517
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv4 1/4] gdb: allow gdb.Color to work correctly with pagination
2025-06-18 19:30 ` [PATCHv4 0/4] " Andrew Burgess
@ 2025-06-18 19:30 ` Andrew Burgess
2025-06-18 19:30 ` [PATCHv4 2/4] gdb/python: add gdb.Style class Andrew Burgess
` (3 subsequent siblings)
4 siblings, 0 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-06-18 19:30 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
This commit allows gdb.Color objects to be used to style output from
GDB command, and that styled output should then work correctly with
pagination.
There are two parts to fixing this functionality.
First, GDB needs to be able to track the currently applied style
within the page_file class. This means that style changes need to be
achieved with calls to pager_file::emit_style_escape.
Now usually, GDB does this by calling something like fprintf_styled,
which takes care to apply the style for us. However, that's not
really an option here as a gdb.Color isn't a full style, and as the
gdb.Color object is designed to be converted directly into escape
sequences that can then be printed, we really need a solution that
works with that approach.
However pager_file::puts already has code in place to handle escape
sequences. Right now all this code does is spot the escape sequence
and append it to the m_wrap_buffer. But in this commit I propose that
we go one step further. GDB already has code in place to parse an
escape sequence back into a ui_file_style object, and so, in
pager_file::puts, if we see an escape sequence, we can try to parse it
back into a ui_file_style, if that works then we can call
pager_file::emit_style_escape. If the parsing doesn't work then we
can just add the escape sequence to m_wrap_buffer as we did before.
Ideally this would be all that was needed. But unfortunately this
doesn't work because....
... the implementation of the Python function gdb.write() calls
gdb_printf(), which calls gdb_vprintf(), which calls ui_file::vprintf,
which calls ui_out::vmessage, which calls ui_out::call_do_message, and
finally we reach cli_ui_out::do_message. This final do_message
function does this:
ui_file *stream = m_streams.back ();
stream->emit_style_escape (style);
stream->puts (str.c_str ());
stream->emit_style_escape (ui_file_style ());
So STR here holds the escape sequence we are writing. The STYLE will
be the default style.
After the above change, when we call pager_file::puts we now modify
the stream's m_applied_style. And now, when we run the final line,
which restores the default style, GDB immediately overwrites our
updated style.
The fix for this is to avoid the high level gdb_printf in the
gdb.write() call, and instead use gdb_puts instead. The gdb_puts
function doesn't try to restore the default style, which means our
style modification survives.
The test includes what appears like a pointless extra loop (looping
over a single value), but later commits will add more values to this
list.
---
gdb/python/python.c | 18 +--
| 116 ++++++++++++++++++
| 46 +++++++
gdb/utils.c | 21 +++-
4 files changed, 189 insertions(+), 12 deletions(-)
create mode 100644 gdb/testsuite/gdb.python/py-color-pagination.exp
create mode 100644 gdb/testsuite/gdb.python/py-color-pagination.py
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 7c3d0d104b5..7a3f688e5aa 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -1570,21 +1570,21 @@ gdbpy_write (PyObject *self, PyObject *args, PyObject *kw)
try
{
+ ui_file *stream;
switch (stream_type)
{
case 1:
- {
- gdb_printf (gdb_stderr, "%s", arg);
- break;
- }
+ stream = gdb_stderr;
+ break;
case 2:
- {
- gdb_printf (gdb_stdlog, "%s", arg);
- break;
- }
+ stream = gdb_stdlog;
+ break;
default:
- gdb_printf (gdb_stdout, "%s", arg);
+ stream = gdb_stdout;
+ break;
}
+
+ gdb_puts (arg, stream);
}
catch (const gdb_exception &except)
{
--git a/gdb/testsuite/gdb.python/py-color-pagination.exp b/gdb/testsuite/gdb.python/py-color-pagination.exp
new file mode 100644
index 00000000000..cb410da2274
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-color-pagination.exp
@@ -0,0 +1,116 @@
+# 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.Color and how this
+# interacts with GDB's pagination system.
+
+load_lib gdb-python.exp
+
+require allow_python_tests
+
+standard_testfile
+
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+
+set str "<[string repeat - 78]>"
+
+set black "(?:\033\\\[30;49;22;27m)"
+set red "(?:\033\\\[31;49;22;27m)"
+set green "(?:\033\\\[32;49;22;27m)"
+set yellow "(?:\033\\\[33;49;22;27m)"
+set blue "(?:\033\\\[34;49;22;27m)"
+set magenta "(?:\033\\\[35;49;22;27m)"
+set cyan "(?:\033\\\[36;49;22;27m)"
+set white "(?:\033\\\[37;49;22;27m)"
+
+set any_color "(?:${black}|${red}|${green}|${yellow}|${blue}|${magenta}|${cyan}|${white})"
+
+# Run the command 'TYPE-fill MODE' which fills the screen with output and
+# triggers the pagination prompt. Check that styling is applied correctly
+# to the output.
+proc test_pagination { type mode } {
+
+ # Start with a fresh GDB, but enable color support.
+ with_ansi_styling_terminal {
+ clean_restart
+ }
+
+ gdb_test_no_output "source $::pyfile" "source the script"
+
+ gdb_test_no_output "set width 80"
+ gdb_test_no_output "set height 15"
+
+ set saw_bad_color_handling false
+ set expected_restore_color ""
+ set last_color ""
+ gdb_test_multiple "$type-fill $mode" "" {
+ -re "^$type-fill $mode\r\n" {
+ exp_continue
+ }
+
+ -re "^(${::any_color}?)(${::any_color})$::str" {
+ # After a continuation prompt GDB will restore the previous
+ # color, and then we immediately switch to a new color.
+ set restored_color $expect_out(1,string)
+ if { $restored_color ne ""
+ && $restored_color ne $expected_restore_color } {
+ set saw_bad_color_handling true
+ }
+ set last_color $expect_out(2,string)
+ exp_continue
+ }
+
+ -re "^\033\\\[${::decimal}m$::str" {
+ # This catches the case where the color's escape sequence has
+ # not been converted back into a full style. This indicates
+ # something went wrong in the pager_file::puts function.
+ set saw_bad_color_handling true
+ exp_continue
+ }
+
+ -re "^((?:\033\\\[m)?)$::pagination_prompt$" {
+ # After a pagination prompt we expect GDB to restore the last
+ # color.
+ set expected_restore_color $last_color
+
+ # If we didn't see a color reset sequence then the pagination
+ # prompt will have been printed in the wrong color, this is a
+ # GDB bug.
+ set color_reset $expect_out(1,string)
+ if { $color_reset eq "" } {
+ set saw_bad_color_handling true
+ }
+
+ # Send '\n' to view more output.
+ send_gdb "\n"
+ exp_continue
+ }
+
+ -re "^\r\n" {
+ # The matches the newline sent to the continuation prompt.
+ exp_continue
+ }
+
+ -re "^\033\\\[m\r\n$::gdb_prompt $" {
+ gdb_assert { !$saw_bad_color_handling } $gdb_test_name
+ }
+ }
+}
+
+foreach_with_prefix type { color } {
+ foreach_with_prefix mode { write print } {
+ test_pagination $type $mode
+ }
+}
--git a/gdb/testsuite/gdb.python/py-color-pagination.py b/gdb/testsuite/gdb.python/py-color-pagination.py
new file mode 100644
index 00000000000..efd501eedf5
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-color-pagination.py
@@ -0,0 +1,46 @@
+# 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/>.
+
+import gdb
+
+basic_colors = ["black", "red", "green", "yellow", "blue", "magenta", "cyan", "white"]
+
+
+def write(mode, text):
+ if mode == "write":
+ gdb.write(text)
+ else:
+ print(text, end="")
+
+
+class ColorTester(gdb.Command):
+ def __init__(self):
+ super().__init__("color-fill", gdb.COMMAND_USER)
+
+ def invoke(self, args, from_tty):
+ mode = args
+ str = "<" + "-" * 78 + ">"
+ for i in range(0, 20):
+ for color_name in basic_colors:
+ c = gdb.Color(color_name)
+ write(mode, c.escape_sequence(True))
+ write(mode, str)
+
+ default = gdb.Color("none")
+ write(mode, default.escape_sequence(True))
+ write(mode, "\n")
+
+
+ColorTester()
diff --git a/gdb/utils.c b/gdb/utils.c
index 4f48e15b7eb..3119ac74260 100644
--- a/gdb/utils.c
+++ b/gdb/utils.c
@@ -1682,10 +1682,25 @@ pager_file::puts (const char *linebuffer)
else if (*linebuffer == '\033'
&& skip_ansi_escape (linebuffer, &skip_bytes))
{
- m_wrap_buffer.append (linebuffer, skip_bytes);
- /* Note that we don't consider this a character, so we
+ /* We don't consider escape sequences as characters, so we
don't increment chars_printed here. */
- linebuffer += skip_bytes;
+
+ size_t style_len;
+ ui_file_style style;
+ if (style.parse (linebuffer, &style_len)
+ && style_len <= skip_bytes)
+ {
+ this->emit_style_escape (style);
+
+ linebuffer += style_len;
+ skip_bytes -= style_len;
+ }
+
+ if (skip_bytes > 0)
+ {
+ m_wrap_buffer.append (linebuffer, skip_bytes);
+ linebuffer += skip_bytes;
+ }
}
else if (*linebuffer == '\r')
{
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv4 2/4] gdb/python: add gdb.Style class
2025-06-18 19:30 ` [PATCHv4 0/4] " Andrew Burgess
2025-06-18 19:30 ` [PATCHv4 1/4] gdb: allow gdb.Color to work correctly with pagination Andrew Burgess
@ 2025-06-18 19:30 ` Andrew Burgess
2025-06-18 19:30 ` [PATCHv4 3/4] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
` (2 subsequent siblings)
4 siblings, 0 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-06-18 19:30 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess, Eli Zaretskii
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 ++++++++++++++++++
| 5 +-
| 20 +
gdb/testsuite/gdb.python/py-style.exp | 342 ++++++++
gdb/ui-style.h | 6 +
8 files changed, 1316 insertions(+), 2 deletions(-)
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 d180c49c834..280043a0ed3 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 f343d0b4457..2fa6e43d4c0 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..b3983af3a85
--- /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 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;
+
+ 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)
+{
+ 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;
+ (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)
+{
+ 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);
--git a/gdb/testsuite/gdb.python/py-color-pagination.exp b/gdb/testsuite/gdb.python/py-color-pagination.exp
index cb410da2274..7b3dc1859c1 100644
--- a/gdb/testsuite/gdb.python/py-color-pagination.exp
+++ b/gdb/testsuite/gdb.python/py-color-pagination.exp
@@ -14,7 +14,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# This file is part of the GDB testsuite. It tests gdb.Color and how this
-# interacts with GDB's pagination system.
+# interacts with GDB's pagination system. The test also tests gdb.Style
+# because the tests are very similar.
load_lib gdb-python.exp
@@ -109,7 +110,7 @@ proc test_pagination { type mode } {
}
}
-foreach_with_prefix type { color } {
+foreach_with_prefix type { color style } {
foreach_with_prefix mode { write print } {
test_pagination $type $mode
}
--git a/gdb/testsuite/gdb.python/py-color-pagination.py b/gdb/testsuite/gdb.python/py-color-pagination.py
index efd501eedf5..f0252e5bf86 100644
--- a/gdb/testsuite/gdb.python/py-color-pagination.py
+++ b/gdb/testsuite/gdb.python/py-color-pagination.py
@@ -43,4 +43,24 @@ class ColorTester(gdb.Command):
write(mode, "\n")
+class StyleTester(gdb.Command):
+ def __init__(self):
+ super().__init__("style-fill", gdb.COMMAND_USER)
+
+ def invoke(self, args, from_tty):
+ mode = args
+ str = "<" + "-" * 78 + ">"
+ for i in range(0, 20):
+ for color_name in basic_colors:
+ c = gdb.Color(color_name)
+ s = gdb.Style(foreground=c)
+ write(mode, s.escape_sequence())
+ write(mode, str)
+
+ default = gdb.Style()
+ write(mode, default.escape_sequence())
+ write(mode, "\n")
+
+
ColorTester()
+StyleTester()
diff --git a/gdb/testsuite/gdb.python/py-style.exp b/gdb/testsuite/gdb.python/py-style.exp
new file mode 100644
index 00000000000..020d0e0319b
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-style.exp
@@ -0,0 +1,342 @@
+# 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"
+
+# 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"
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
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv4 3/4] gdb/python: new class gdb.StyleParameterSet
2025-06-18 19:30 ` [PATCHv4 0/4] " Andrew Burgess
2025-06-18 19:30 ` [PATCHv4 1/4] gdb: allow gdb.Color to work correctly with pagination Andrew Burgess
2025-06-18 19:30 ` [PATCHv4 2/4] gdb/python: add gdb.Style class Andrew Burgess
@ 2025-06-18 19:30 ` Andrew Burgess
2025-06-18 19:30 ` [PATCHv4 4/4] gdb: extend gdb.write to support styled output Andrew Burgess
2025-06-25 14:42 ` [PATCHv5 0/4] Python Style API Andrew Burgess
4 siblings, 0 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-06-18 19:30 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
Add a new helper class gdb.StyleParameterSet. This new class can be
used to simplify creation of new style parameter sets. A style
parameter set is the 'foreground', 'background', and (optionally), the
'intensity' settings, all grouped under a single prefix command.
And example usage is:
(gdb) python s = gdb.StyleParameterSet("my-style")
(gdb) show style my-style
style my-style background: The "my-style" style background color is: none
style my-style foreground: The "my-style" style foreground color is: none
style my-style intensity: The "my-style" style display intensity is: normal
(gdb)
Having created a gdb.StyleParameterSet, the object itself can be used
to access a named style corresponding to the setting group, like this:
(gdb) python print(s.style)
<gdb.Style name='my-style', fg=none, bg=none, intensity=normal>
(gdb)
Of course, having access to the gdb.Style makes it easy to change the
settings, or the settings can be adjusted via the normal CLI 'set'
commands.
As gdb.StyleParameterSet manages a set of parameters, and the
gdb.Parameter class uses Parameter.value as the attribute to read the
parameter's value, there is also StyleParameterSet.value, but this is
just an alias for StyleParameterSet.style, that is, it allows the
gdb.Style object to be read and written too.
It is worth noting that this class only creates a single level of
prefix command. As an example GDB has style 'disassembler mnemonic',
where the 'disassembler' part is a group of related styles. If a user
wanted to create:
style
my-style-group
style-1
style-2
style-3
Where each of 'style-1', 'style-2', and 'style-3' will have the full
set of 'foreground', 'background', and 'intensity', then the
gdb.StyleParameterSet can be used to create the 'style-N' part, but
the user will have to create the 'my-style-group' prefix themselves,
possibly using gdb.ParameterPrefix, e.g.:
gdb.ParameterPrefix("style my-style-group", gdb.COMMAND_NONE)
gdb.StyleParameterSet("my-style-group style-1")
gdb.StyleParameterSet("my-style-group style-2")
gdb.StyleParameterSet("my-style-group style-3")
---
gdb/NEWS | 4 +
gdb/doc/python.texi | 115 +++++-
gdb/python/lib/gdb/__init__.py | 209 ++++++++++
.../gdb.python/py-style-parameter-set.exp | 366 ++++++++++++++++++
4 files changed, 693 insertions(+), 1 deletion(-)
create mode 100644 gdb/testsuite/gdb.python/py-style-parameter-set.exp
diff --git a/gdb/NEWS b/gdb/NEWS
index 280043a0ed3..886a933cd54 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -138,6 +138,10 @@ info threads [-gid] [-stopped] [-running] [ID]...
gdb.INTENSITY_DIM for use with gdb.Style when representing
intensities.
+ ** New gdb.StyleParameterSet for creating custom style settings.
+ Use gdb.StyleParameterSet(NAME) to create 'set style NAME ...'
+ and 'show style NAME ...' parameters.
+
** 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 2fa6e43d4c0..c91f9578a93 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -5282,6 +5282,7 @@ Parameters In Python
feature-2}, then the @kbd{plugin-name} would need to be a prefix
command (@pxref{CLI Commands In Python}).
+@anchor{gdb.ParameterPrefix}
However, when creating parameters, you will almost always need to
create two prefix commands, one as a @kbd{set} sub-command, and one as
a @kbd{show} sub-command. @value{GDBN} provides the
@@ -5368,6 +5369,117 @@ Parameters In Python
ExampleParam("plugin-name feature-2")
@end smallexample
+@anchor{Creating Style Parameters}
+The helper class @code{gdb.StyleParameterSet} exists to make it easier
+to create new styles. @value{GDBN} places style settings under
+@samp{show style @dots{}} and @samp{set style @dots{}}, an example of a style
+is @samp{filename}. Each style is really a prefix command (@pxref{CLI
+Commands In Python}), with sub-commands @samp{foreground},
+@samp{background}, and optionally, @samp{intensity}.
+
+It is simple enough to create a new style using two @code{gdb.Command}
+objects for the prefix commands (one for @samp{set}, and one for
+@samp{show}), and three @code{gdb.Parameter} objects, one each for the
+@samp{foreground}, @samp{background}, and @samp{intensity}. You would
+also want to take care to craft the help text so that the new style
+behaves the same as the existing styles.
+
+Or, you can use the @code{gdb.StyleParameterSet} class, which takes
+care of all this, as the following example shows:
+
+@smallexample
+@group
+(@value{GDBP}) python s = gdb.StyleParameterSet("my-style")
+(@value{GDBP}) show style my-style
+style my-style background: The "my-style" style background color is: none
+style my-style foreground: The "my-style" style foreground color is: none
+style my-style intensity: The "my-style" style display intensity is: normal
+(@value{GDBP})
+@end group
+@end smallexample
+
+You might also want to group a number of styles within a new prefix,
+similar to how @value{GDBN} groups disassembler related styles within
+the @samp{style disassembler} prefix. This can be done using
+@code{gdb.ParameterPrefix} (@pxref{gdb.ParameterPrefix}), as in this
+example:
+
+@smallexample
+@group
+(@value{GDBP}) python gdb.ParameterPrefix("style my-group", gdb.COMMAND_NONE)
+(@value{GDBP}) python s_a = gdb.StyleParameterSet("my-group aaa")
+(@value{GDBP}) python s_b = gdb.StyleParameterSet("my-group bbb")
+(@value{GDBP}) show style my-group
+style my-group aaa background: The "my-group aaa" style background color is: none
+style my-group aaa foreground: The "my-group aaa" style foreground color is: none
+style my-group aaa intensity: The "my-group aaa" style display intensity is: normal
+style my-group bbb background: The "my-group bbb" style background color is: none
+style my-group bbb foreground: The "my-group bbb" style foreground color is: none
+style my-group bbb intensity: The "my-group bbb" style display intensity is: normal
+(@value{GDBP})
+@end group
+@end smallexample
+
+The @code{gdb.StyleParameterSet} class has the following methods and
+attributes:
+
+@defun StyleParameterSet.__init__(name, @w{add_intensity=@code{True}}, @w{doc=@code{None}})
+Create a new style group based on @var{name}, which is a string. For
+example if @var{name} is @samp{my-style}, then @value{GDBN} will
+create the prefix commands @samp{set style my-style} and @samp{show
+style my-style}. Within these prefix commands will be
+@samp{foreground}, @samp{background}, and @samp{intensity} parameters
+with the appropriate types.
+
+It is also possible for @var{name} to consist of multiple words, so
+long as each prefix command (except the last one) already exists. For
+example, it is valid to use a @var{name} value of @samp{disassembler
+my-style}, as the @samp{disassembler} prefix command already exists.
+@value{GDBN} would then create @samp{set style disassembler my-style}
+and @samp{show style disassembler my-style}, and within the
+@samp{my-style} prefixes will be the @samp{foreground},
+@samp{background}, and @samp{intensity} parameters with the
+appropriate types.
+
+Every style requires a @samp{foreground} and @samp{background}, but
+not every style needs an @samp{intensity}. If @var{add_intensity} is
+@code{True} (the default), then the @samp{intensity} parameter will be
+created. If @var{add_intensity} is @code{False}, then the
+@samp{intensity} parameter will not be created.
+
+If the @samp{intensity} parameter is not created, then the
+@code{gdb.Style} (@pxref{Styles In Python}) created from this
+@code{gdb.StyleParameterSet} will have @code{gdb.INTENSITY_NORMAL}.
+
+The @var{doc} should be a string which will be used as the help text
+for the @var{name} prefix command. This text is used as the
+@code{Command.__doc__} value for the @code{gdb.Command} object that is
+the prefix command object (@pxref{CLI Commands In Python}). If
+@var{doc} is @code{None} (the default) then a basic default help text
+is used.
+@end defun
+
+@defun StyleParameterSet.apply(string)
+Equivalent to @code{StyleParameterSet.style.apply(string)}. Returns a
+copy of @var{string} with escape sequences added to the start and end.
+The escape sequence at the start applies this style, and the escape
+sequence at the end restores the terminal default.
+
+If styling is disabled (i.e.@: @samp{set style enabled off}), then no
+escape sequences are added and this method returns a copy of
+@var{string}.
+@end defun
+
+@defvar StyleParameterSet.style
+This read/write attribute holds a @code{gdb.Style} object
+(@pxref{Styles In Python}), that is a named style associated with this
+style parameter group.
+@end defvar
+
+@defvar StyleParameterSet.value
+This is an alias for @code{StyleParameterSet.style}, see above.
+@end defvar
+
@node Functions In Python
@subsubsection Writing new convenience functions
@@ -7264,7 +7376,8 @@ Styles In Python
@value{GDBN} has many styles builtin (@pxref{Output Styling}), and
style objects can be created that apply these builtin styles to Python
-output.
+output. It is also possible to create new styles which can be used to
+style Python output (@pxref{Creating Style Parameters}).
The style class is called @code{gdb.Style}, and has the following
methods and attributes:
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index cedd897ab0f..f2f57280007 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -515,3 +515,212 @@ class ParameterPrefix:
def dont_repeat(self):
if self.active_prefix is not None:
self.active_prefix.dont_repeat()
+
+
+class StyleParameterSet:
+ """Create new style parameters.
+
+ A style parameter is a set of parameters that start with 'set style ...'
+ and 'show style ...'. For example 'filename' is a style parameter, and
+ 'disassembler symbol' is another style parameter.
+
+ The name of the style parameter is really a prefix command. Under this
+ we must have two commands 'foreground' and 'background', which are color
+ parameters. A third, optional command 'intensity', is an enum with
+ values 'normal', 'bold', and 'dim'.
+
+ A StyleParameterSet is initialised with a name, e.g. 'filename' or
+ 'disassembler symbol'. The StyleParameterSet creates the prefix
+ commands in the 'set style' and 'show style' name space, and then adds
+ the 'foreground', 'background', and optionally, the 'intensity'
+ commands.
+
+ If you want a whole new style group, similar to 'disassembler',
+ then you need to add this yourself first, then StyleParameterSet
+ can be used to create styles within the new prefix group.
+
+ The 'value' attribute on this object can be used to get and set a
+ gdb.Style object which controls all aspects of this style.
+
+ For readability, the alias 'style' is the same as 'value'.
+ """
+
+ def __init__(self, name, add_intensity=True, doc=None):
+ # The STYLE_NAME is something like 'filename' is 'set style
+ # filename ...', and PARAM_NAME is one of 'foreground',
+ # 'background', or 'intensity'. The DESC_TEXT is the long
+ # form used in help text, like 'foreground color' or 'display
+ # intensity'. The DEFAULT_VALUE is used to set the SELF.value
+ # attribute. And PARAM_TYPE is a gdb.PARAM_* constant. The
+ # ARGS is used for gdb.PARAM_ENUM, which ARGS should be the
+ # enum value list.
+ class style_parameter(Parameter):
+ def __init__(
+ self,
+ style_name,
+ parent_obj,
+ param_name,
+ desc_text,
+ default_value,
+ param_type,
+ *args
+ ):
+ # Setup documentation must be done before calling
+ # parent's __init__ method, as the __init__ reads (and
+ # copies) these values.
+ self.show_doc = "Show the " + desc_text + " for this property."
+ self.set_doc = "Set the " + desc_text + " for this property."
+ self.__doc__ = ""
+
+ # Call the parent's __init__ method to actually create
+ # the parameter.
+ super().__init__(
+ "style " + style_name + " " + param_name,
+ COMMAND_NONE,
+ param_type,
+ *args
+ )
+
+ # Store information we need in other methods.
+ self._style_name = style_name
+ self._desc_text = desc_text
+ self._parent = parent_obj
+
+ # Finally, setup the default value.
+ self.value = default_value
+
+ # Return the 'show style <style-name> <attribute>' string,
+ # which has styling applied.
+ def get_show_string(self, value):
+ s = self._parent.style
+ return (
+ "The "
+ + s.apply('"' + self._style_name + '" style')
+ + " "
+ + self._desc_text
+ + " is: "
+ + value
+ )
+
+ class style_foreground_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "foreground",
+ "foreground color",
+ Color(),
+ PARAM_COLOR,
+ )
+
+ class style_background_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "background",
+ "background color",
+ Color(),
+ PARAM_COLOR,
+ )
+
+ class style_intensity_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "intensity",
+ "display intensity",
+ "normal",
+ PARAM_ENUM,
+ ["normal", "bold", "dim"],
+ )
+
+ if doc is None:
+ doc = (
+ "The "
+ + name
+ + " display styling.\nConfigure "
+ + name
+ + " colors and display intensity."
+ )
+
+ ParameterPrefix("style " + name, COMMAND_NONE, doc)
+ self._foreground = style_foreground_parameter(name, self)
+ self._background = style_background_parameter(name, self)
+ if add_intensity:
+ self._intensity = style_intensity_parameter(name, self)
+ self._name = name
+ self._style = None
+
+ @property
+ def value(self):
+ """Return the gdb.Style object for this parameter set."""
+ if self._style is None:
+ self._style = Style(self._name)
+ return self._style
+
+ @property
+ def style(self):
+ """Return the gdb.Style object for this parameter set.
+
+ This is an alias for self.value."""
+ return self.value
+
+ @value.setter
+ def value(self, new_value):
+ """Set this parameter set to NEW_VALUE, a gdb.Style object.
+
+ The attributes of NEW_VALUE are used to update the current settings
+ of this parameter set. If this parameter set was created without
+ an intensity setting, then the intensity of NEW_VALUE is ignored."""
+ if not isinstance(new_value, Style):
+ raise TypeError("value must be gdb.Style, not %s" % type(new_value))
+ self._foreground.value = new_value.foreground
+ self._background.value = new_value.background
+ if hasattr(self, "_intensity"):
+ intensity_value = new_value.intensity
+ if intensity_value == INTENSITY_BOLD:
+ intensity_string = "bold"
+ elif intensity_value == INTENSITY_DIM:
+ intensity_string = "dim"
+ elif intensity_value == INTENSITY_NORMAL:
+ intensity_string = "normal"
+ else:
+ raise ValueError(
+ "unknown intensity value %d from Style" % intensity_value
+ )
+
+ self._intensity.value = intensity_string
+
+ @style.setter
+ def style(self, new_value):
+ """Set this parameter set to NEW_VALUE, a gdb.Style object.
+
+ This is an alias for self.value."""
+ self.value = new_value
+
+ def apply(self, *args, **kwargs):
+ """Apply this style to the arguments.
+
+ Forwards all arguments to self.style.apply(). The arguments should be a string,
+ to which this style is applied. This function returns the same string with
+ escape sequences added to apply this style.
+
+ If styling is globally disabled ('set style enabled off') then no escape sequences
+ will be addedded, the input string is returned."""
+ return self.style.apply(*args, **kwargs)
+
+ def __repr__(self):
+ """A string representation of SELF."""
+
+ def full_typename(obj):
+ module = type(obj).__module__
+ qualname = type(obj).__qualname__
+
+ if module is None or module == "builtins":
+ return qualname
+ else:
+ return module + "." + qualname
+
+ return "<" + full_typename(self) + " name='" + self._name + "'>"
diff --git a/gdb/testsuite/gdb.python/py-style-parameter-set.exp b/gdb/testsuite/gdb.python/py-style-parameter-set.exp
new file mode 100644
index 00000000000..9e05acb84cc
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-style-parameter-set.exp
@@ -0,0 +1,366 @@
+# 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.StyleParameterSet.
+
+load_lib gdb-python.exp
+
+require allow_python_tests
+
+# Create a regexp that can be used to match the output of a 'show style
+# NAME' command. HAS_INTENSITY is a boolean and indicates if style NAME has
+# an intensity attribute.
+proc gen_show_style_re { name has_intensity } {
+ set output \
+ [list \
+ "style ${name} background: The \"${name}\" style background color is: none" \
+ "style ${name} foreground: The \"${name}\" style foreground color is: none"]
+
+ if { $has_intensity } {
+ lappend output \
+ "style ${name} intensity: The \"${name}\" style display intensity is: normal"
+ }
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'set style NAME' command,
+# that is, a 'set' command that doesn't actually set an attribute of a
+# style, in this case GDB will print some useful help text. HAS_INTENSITY is
+# a boolean and indicates if style NAME has an intensity attribute or not.
+proc gen_set_style_re { name has_intensity } {
+ set output \
+ [list \
+ "List of \"set style $name\" subcommands:" \
+ "" \
+ "set style $name background -- Set the background color for this property\\." \
+ "set style $name foreground -- Set the foreground color for this property\\."]
+
+ if { $has_intensity } {
+ lappend output \
+ "set style $name intensity -- Set the display intensity for this property\\."
+ }
+
+ lappend output \
+ "" \
+ "Type \"help set style $name\" followed by subcommand name for full documentation\\." \
+ "Type \"apropos word\" to search for commands related to \"word\"\\." \
+ "Type \"apropos -v word\" for full documentation of commands related to \"word\"\\." \
+ "Command name abbreviations are allowed if unambiguous\\."
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'help show style NAME'
+# command. HAS_INTENSITY is a boolean and indicates if style NAME has an
+# intensity attribute or not. DOC is a regexp that matches the doc string
+# for style NAME.
+proc gen_help_show_style_re { name has_intensity doc } {
+ set output \
+ [list \
+ $doc \
+ "" \
+ "List of \"show style ${name}\" subcommands:" \
+ "" \
+ "show style $name background -- Show the background color for this property\\." \
+ "show style $name foreground -- Show the foreground color for this property\\."]
+ if { $has_intensity } {
+ lappend output \
+ "show style $name intensity -- Show the display intensity for this property\\."
+ }
+
+ lappend output \
+ "" \
+ "Type \"help show style $name\" followed by subcommand name for full documentation\\." \
+ "Type \"apropos word\" to search for commands related to \"word\"\\." \
+ "Type \"apropos -v word\" for full documentation of commands related to \"word\"\\." \
+ "Command name abbreviations are allowed if unambiguous\\."
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'help set style NAME'
+# command. HAS_INTENSITY is a boolean and indicates if style NAME has an
+# intensity attribute or not. DOC is a regexp that matches the doc string
+# for style NAME.
+proc gen_help_set_style_re { name has_intensity doc } {
+ return \
+ [multi_line \
+ $doc \
+ "" \
+ [gen_set_style_re $name $has_intensity]]
+}
+
+# Create styles with and without intensity. Use named and unnamed
+# argument passing, and vary the argument passing order. Create
+# styles with and without documentation.
+#
+# Confirm that the styles contain the expected sub-commands, and that
+# the documentation is as expected.
+proc_with_prefix test_basic_usage {} {
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"style-1\")" \
+ "create style-1"
+
+ gdb_test_no_output \
+ "python style_2 = gdb.StyleParameterSet(add_intensity=True, name=\"style-2\")" \
+ "create style-2"
+
+ gdb_test_no_output \
+ "python style_3 = gdb.StyleParameterSet(name=\"style-3\", doc=\"Style-3 display styling.\\nThis is a multi-line documentation\\nstring describing style-3.\")" \
+ "create style-3"
+
+ gdb_test_no_output \
+ "python style_4 = gdb.StyleParameterSet(\"style-4\", add_intensity=False)" \
+ "create style-4"
+
+ foreach style { style-1 style-2 style-3 } {
+ gdb_test "show style $style" \
+ [gen_show_style_re $style true]
+ gdb_test "set style $style" \
+ [gen_set_style_re $style true]
+ }
+
+ gdb_test "show style style-4" \
+ [gen_show_style_re "style-4" false]
+
+ foreach style { style-1 style-2 } {
+ gdb_test "help show style $style" \
+ [gen_help_show_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+
+
+ set out [gen_help_set_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+
+ gdb_test "help set style $style" \
+ [gen_help_set_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+ }
+
+ gdb_test "help show style style-3" \
+ [gen_help_show_style_re "style-3" true \
+ [multi_line \
+ "Style-3 display styling\\." \
+ "This is a multi-line documentation" \
+ "string describing style-3\\."]]
+
+ gdb_test "help show style style-4" \
+ [gen_help_show_style_re "style-4" false \
+ [multi_line \
+ "The style-4 display styling\\." \
+ "Configure style-4 colors and display intensity\\."]]
+
+ for { set i 1 } { $i < 5 } { incr i } {
+ gdb_test "python print(style_$i)" \
+ "<gdb.StyleParameterSet name='style-$i'>" \
+ "print repr of style_$i"
+ }
+
+ # There is no 'style-4 intensity' property.
+ gdb_test "show style style-4 foreground" \
+ "The \"style-4\" style foreground color is: none"
+ gdb_test "show style style-4 background" \
+ "The \"style-4\" style background color is: none"
+ gdb_test "show style style-4 intensity" \
+ "Undefined show style style-4 command: \"intensity\"\\. Try \"help show style style-4\"\\."
+
+ # There is a 'style-1 intensity' property.
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: none" \
+ "show style-1 foreground before changes"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: none" \
+ "show style-1 background before changes"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: normal" \
+ "show style-1 intensity before changes"
+
+ # Grab the gdb.Style objects from 'style-1'.
+ gdb_test_no_output "python s1 = style_1.style"
+ gdb_test_no_output "python s2 = style_1.value"
+
+ # Check both represent the same style.
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=none, bg=none, intensity=normal>" \
+ "print s1 style before changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=none, bg=none, intensity=normal>" \
+ "print s2 style before changes"
+
+ gdb_test_no_output \
+ "python s1.foreground=gdb.Color('red')" "set foreground"
+ gdb_test_no_output \
+ "python s1.background=gdb.Color('blue')" "set background"
+ gdb_test_no_output \
+ "python s1.intensity=gdb.INTENSITY_DIM" "set intensity"
+
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=red, bg=blue, intensity=dim>" \
+ "print s1 style after first set of changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=red, bg=blue, intensity=dim>" \
+ "print s2 style after first set of changes"
+
+ # Check the style properties have updated.
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: red" \
+ "show style-1 foreground after first set of changes"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: blue" \
+ "show style-1 background after first set of changes"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: dim" \
+ "show style-1 intensity after first set of changes"
+
+ # Change the style properties, check gdb.Style objects update.
+ gdb_test_no_output "set style style-1 foreground yellow"
+ gdb_test_no_output "set style style-1 background cyan"
+ gdb_test_no_output "set style style-1 intensity bold"
+
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=yellow, bg=cyan, intensity=bold>" \
+ "print s1 style after second set of changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=yellow, bg=cyan, intensity=bold>" \
+ "print s2 style after second set of changes"
+
+ # Assign a gdb.Style to set 'style-1'. First create some unnamed
+ # style objects that can be used.
+ gdb_test_no_output \
+ "python uns1 = gdb.Style(foreground=gdb.Color('white'), background=gdb.Color('black'), intensity=gdb.INTENSITY_BOLD)" \
+ "create uns1"
+ gdb_test_no_output \
+ "python uns2 = gdb.Style(foreground=gdb.Color('black'), background=gdb.Color('white'), intensity=gdb.INTENSITY_DIM)" \
+ "create uns2"
+
+ gdb_test_no_output "python style_1.value = uns1"
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: white" \
+ "show style-1 foreground after assigning uns1"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: black" \
+ "show style-1 background after assigning uns1"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: bold" \
+ "show style-1 intensity after assigning uns1"
+
+ gdb_test_no_output "python style_1.style = uns2"
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: black" \
+ "show style-1 foreground after assigning uns2"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: white" \
+ "show style-1 background after assigning uns2"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: dim" \
+ "show style-1 intensity after assigning uns2"
+
+ # Assign a style with an intensity that is not 'NORMAL' to a
+ # StyleParameterSet that doesn't have an intensity. The new
+ # intensity setting should be ignored.
+ gdb_test_no_output "python style_4.style = uns1"
+ gdb_test "show style style-4 foreground" \
+ "The \"style-4\" style foreground color is: white" \
+ "show style-4 foreground after assigning uns1"
+ gdb_test "show style style-4 background" \
+ "The \"style-4\" style background color is: black" \
+ "show style-4 background after assigning uns1"
+ gdb_test "show style style-4 intensity" \
+ "Undefined show style style-4 command: \"intensity\"\\. Try \"help show style style-4\"\\." \
+ "show style-4 intensity after assigning uns1"
+
+ gdb_test "python print(style_4.style)" \
+ "<gdb.Style name='style-4', fg=white, bg=black, intensity=normal>" \
+ "print string repr of style_4's style"
+}
+
+# Test creating a style prefix with gdb.ParameterPrefix, then adding
+# some styles within the new prefix. Change the style through the CLI
+# and confirm that the associated Python object updated as expected.
+proc_with_prefix test_style_prefix {} {
+ gdb_test_no_output \
+ "python gdb.ParameterPrefix(\"style my-style-group\", gdb.COMMAND_NONE)"
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"my-style-group style-1\")" \
+ "create style-1"
+ gdb_test_no_output \
+ "python style_2 = gdb.StyleParameterSet(\"my-style-group style-2\")" \
+ "create style-2"
+
+ gdb_test "python print(style_1.style)" \
+ "<gdb.Style name='my-style-group style-1', fg=none, bg=none, intensity=normal>" \
+ "print 'my-style-group style-1' style before changes"
+ gdb_test "python print(style_2.style)" \
+ "<gdb.Style name='my-style-group style-2', fg=none, bg=none, intensity=normal>" \
+ "print 'my-style-group style-2' style before changes"
+
+ gdb_test_no_output "set style my-style-group style-1 foreground red"
+ gdb_test_no_output "set style my-style-group style-1 background yellow"
+ gdb_test_no_output "set style my-style-group style-1 intensity bold"
+ gdb_test_no_output "set style my-style-group style-2 foreground black"
+ gdb_test_no_output "set style my-style-group style-2 background blue"
+ gdb_test_no_output "set style my-style-group style-2 intensity dim"
+
+ gdb_test "python print(style_1.style)" \
+ "<gdb.Style name='my-style-group style-1', fg=red, bg=yellow, intensity=bold>" \
+ "print 'my-style-group style-1' style after changes"
+ gdb_test "python print(style_2.style)" \
+ "<gdb.Style name='my-style-group style-2', fg=black, bg=blue, intensity=dim>" \
+ "print 'my-style-group style-2' style after changes"
+}
+
+# Test that gdb.StyleParameterSet.apply() works as expected.
+proc_with_prefix test_applying {} {
+ # Create a new StyleParameterSet, and adjust its settings.
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"style-1\")" \
+ "create style-1"
+ gdb_test_no_output \
+ "python uns1 = gdb.Style(foreground=gdb.Color('red'), background=gdb.Color('blue'), intensity=gdb.INTENSITY_BOLD)" \
+ "create uns1"
+ gdb_test_no_output "python style_1 = uns1"
+
+ # When styling is off (which it currently is), no escape sequences
+ # should be added.
+ gdb_test \
+ "python print(style_1.apply('xxx'))" "^xxx" \
+ "apply StyleParameterSet to a string when styling is off"
+
+ # When styling is on, we should see an escape sequence added.
+ gdb_test "with style enabled on -- python print(style_1.apply('xxx'))" \
+ "\033\\\[31;44;1;27mxxx\033\\\[m" \
+ "apply a style when styling is on"
+}
+
+# Start GDB.
+with_ansi_styling_terminal {
+ clean_restart
+}
+
+# Turn styling off so that the output of 'show style ...' isn't styled, this
+# makes it easier to match the output.
+gdb_test_no_output "set style enabled off"
+
+# Run the tests.
+test_basic_usage
+test_style_prefix
+test_applying
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv4 4/4] gdb: extend gdb.write to support styled output
2025-06-18 19:30 ` [PATCHv4 0/4] " Andrew Burgess
` (2 preceding siblings ...)
2025-06-18 19:30 ` [PATCHv4 3/4] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
@ 2025-06-18 19:30 ` Andrew Burgess
2025-06-25 14:42 ` [PATCHv5 0/4] Python Style API Andrew Burgess
4 siblings, 0 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-06-18 19:30 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
It is already possible to produce styled output from Python by
converting the gdb.Style to its escape code sequence, and writing that
to the output stream.
But this commit adds an alternative option to the mix by extending the
existing gdb.write() function to accept a 'style' argument. The value
of this argument can be 'None' to indicate no style change should be
performed, this is the default, and matches the existing behaviour.
Or the new 'style' argument can be a gdb.Style object, in which case
the specified style is applied only for the string passed to
gdb.write, after which the default style is re-applied.
Using gdb.write with a style object more closely matches how GDB
handles styling internally, and has the benefit that the user doesn't
need to remember to restore the default style when they are done.
---
gdb/NEWS | 3 ++
gdb/doc/python.texi | 9 +++-
gdb/python/py-style.c | 27 ++++++++++
gdb/python/python-internal.h | 15 ++++++
gdb/python/python.c | 30 ++++++++++--
| 49 +++++++++++++++++++
| 16 ++++++
gdb/testsuite/gdb.python/py-style.exp | 29 +++++++++++
8 files changed, 172 insertions(+), 6 deletions(-)
diff --git a/gdb/NEWS b/gdb/NEWS
index 886a933cd54..0c1f95c48e0 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -142,6 +142,9 @@ info threads [-gid] [-stopped] [-running] [ID]...
Use gdb.StyleParameterSet(NAME) to create 'set style NAME ...'
and 'show style NAME ...' parameters.
+ ** The gdb.write() function now takes an additional, optional,
+ 'style' argument, which can be used to style the output.
+
** 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 c91f9578a93..99c1672d44a 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -452,7 +452,7 @@ Basic Python
historical compatibility.
@end defun
-@defun gdb.write (string @r{[}, stream@r{]})
+@defun gdb.write (string @r{[}, stream@r{]} @r{[}, style@r{]})
Print a string to @value{GDBN}'s paginated output stream. The
optional @var{stream} determines the stream to print to. The default
stream is @value{GDBN}'s standard output stream. Possible stream
@@ -475,6 +475,13 @@ Basic Python
@value{GDBN}'s log stream (@pxref{Logging Output}).
@end table
+The @var{style} should be a @code{gdb.Style} object (@pxref{Styles In
+Python}), or @code{None} (the default). If @var{style} is @code{None}
+then the current style for @var{stream} will be applied to @var{text}.
+If @var{style} is a @code{gdb.Style} object, then this style is
+applied to @var{text}, after which the default output style is
+restored.
+
Writing to @code{sys.stdout} or @code{sys.stderr} will automatically
call this function and will automatically direct the output to the
relevant stream.
diff --git a/gdb/python/py-style.c b/gdb/python/py-style.c
index b3983af3a85..0e9af1fc4c4 100644
--- a/gdb/python/py-style.c
+++ b/gdb/python/py-style.c
@@ -353,6 +353,15 @@ stylepy_init (PyObject *self, PyObject *args, PyObject *kwargs)
\f
+/* See python-internal.h. */
+
+bool
+gdbpy_is_style (PyObject *obj)
+{
+ gdb_assert (obj != nullptr);
+ return PyObject_TypeCheck (obj, &style_object_type);
+}
+
/* Return the ui_file_style for STYLEPY. If the style cannot be found,
then return an empty optional, and set a Python error. */
@@ -369,6 +378,24 @@ stylepy_to_style (style_object *stylepy)
return style;
}
+/* See python-internal.h. */
+
+std::optional<ui_file_style>
+gdbpy_style_object_to_ui_file_style (PyObject *obj)
+{
+ gdb_assert (obj != nullptr);
+ if (!gdbpy_is_style (obj))
+ {
+ PyErr_Format
+ (PyExc_TypeError, _("argument must be a gdb.Style object, not %s."),
+ Py_TYPE (obj)->tp_name);
+ return {};
+ }
+
+ style_object *style_obj = (style_object *) obj;
+ return stylepy_to_style (style_obj);
+}
+
/* 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
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index 7f4237eecc2..28199691226 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -565,6 +565,21 @@ struct symtab_and_line *sal_object_to_symtab_and_line (PyObject *obj);
frame_info_ptr frame_object_to_frame_info (PyObject *frame_obj);
struct gdbarch *arch_object_to_gdbarch (PyObject *obj);
+/* Return true if OBJ is a gdb.Style object. OBJ must not be NULL. */
+
+extern bool gdbpy_is_style (PyObject *obj);
+
+/* Return the ui_file_style from OBJ, a gdb.Style object. OBJ must not be
+ NULL, but can be a non gdb.Style object, in which case a Python error is
+ set and an empty optional is returned.
+
+ It is also possible that OBJ is a gdb.Style object, but the underlying
+ style cannot be fetched for some reason. If this happens then a Python
+ error is set and an empty optional is returned. */
+
+extern std::optional<ui_file_style>
+ gdbpy_style_object_to_ui_file_style (PyObject *obj);
+
extern PyObject *gdbpy_execute_mi_command (PyObject *self, PyObject *args,
PyObject *kw);
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 7a3f688e5aa..eb24b10aa1f 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -1561,12 +1561,22 @@ static PyObject *
gdbpy_write (PyObject *self, PyObject *args, PyObject *kw)
{
const char *arg;
- static const char *keywords[] = { "text", "stream", NULL };
+ static const char *keywords[] = { "text", "stream", "style", nullptr };
int stream_type = 0;
+ PyObject *style_obj = Py_None;
- if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s|i", keywords, &arg,
- &stream_type))
- return NULL;
+ if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s|iO", keywords, &arg,
+ &stream_type, &style_obj))
+ return nullptr;
+
+ if (style_obj != Py_None && !gdbpy_is_style (style_obj))
+ {
+ PyErr_Format
+ (PyExc_TypeError,
+ _("'style' argument must be gdb.Style or None, not %s."),
+ Py_TYPE (style_obj)->tp_name);
+ return nullptr;
+ }
try
{
@@ -1584,7 +1594,17 @@ gdbpy_write (PyObject *self, PyObject *args, PyObject *kw)
break;
}
- gdb_puts (arg, stream);
+ if (style_obj == Py_None)
+ gdb_puts (arg, stream);
+ else
+ {
+ std::optional<ui_file_style> style
+ = gdbpy_style_object_to_ui_file_style (style_obj);
+ if (!style.has_value ())
+ return nullptr;
+
+ fputs_styled (arg, style.value (), stream);
+ }
}
catch (const gdb_exception &except)
{
--git a/gdb/testsuite/gdb.python/py-color-pagination.exp b/gdb/testsuite/gdb.python/py-color-pagination.exp
index 7b3dc1859c1..0a2f01e6533 100644
--- a/gdb/testsuite/gdb.python/py-color-pagination.exp
+++ b/gdb/testsuite/gdb.python/py-color-pagination.exp
@@ -110,8 +110,57 @@ proc test_pagination { type mode } {
}
}
+# Run the command 'style-fill-v2' which fills the screen with output and
+# triggers the pagination prompt. Check that styling is applied correctly
+# to the output. This v2 command is exercising passing a style to
+# gdb.write() rather than passing the escape sequence for the style.
+proc test_pagination_v2 { } {
+ set saw_bad_color_handling false
+ set expected_restore_color ""
+ set last_color ""
+ gdb_test_multiple "style-fill-v2" "" {
+ -re "^style-fill-v2\r\n" {
+ exp_continue
+ }
+
+ -re "^((?:${::any_color}\033\\\[m)?)(${::any_color})$::str\033\\\[m" {
+ # After a continuation prompt GDB will restore the previous
+ # color, and then we immediately switch to a new color.
+ set restored_color $expect_out(1,string)
+ if { $restored_color ne ""
+ && $restored_color ne $expected_restore_color } {
+ set saw_bad_color_handling true
+ }
+ set last_color $expect_out(2,string)
+ exp_continue
+ }
+
+ -re "^$::pagination_prompt$" {
+ # After a pagination prompt we expect GDB to restore the last
+ # color, but this will then be disabled due to a styled
+ # gdb.write emitting a return to default style escape sequence.
+ set expected_restore_color "$last_color\033\[m"
+
+ # Send '\n' to view more output.
+ send_gdb "\n"
+ exp_continue
+ }
+
+ -re "^\r\n" {
+ # The matches the newline sent to the continuation prompt.
+ exp_continue
+ }
+
+ -re "^$::gdb_prompt $" {
+ gdb_assert { !$saw_bad_color_handling } $gdb_test_name
+ }
+ }
+}
+
foreach_with_prefix type { color style } {
foreach_with_prefix mode { write print } {
test_pagination $type $mode
}
}
+
+test_pagination_v2
--git a/gdb/testsuite/gdb.python/py-color-pagination.py b/gdb/testsuite/gdb.python/py-color-pagination.py
index f0252e5bf86..9cdc76cb0ce 100644
--- a/gdb/testsuite/gdb.python/py-color-pagination.py
+++ b/gdb/testsuite/gdb.python/py-color-pagination.py
@@ -62,5 +62,21 @@ class StyleTester(gdb.Command):
write(mode, "\n")
+class StyleTester2(gdb.Command):
+ def __init__(self):
+ super().__init__("style-fill-v2", gdb.COMMAND_USER)
+
+ def invoke(self, args, from_tty):
+ str = "<" + "-" * 78 + ">"
+ for i in range(0, 20):
+ for color_name in basic_colors:
+ c = gdb.Color(color_name)
+ s = gdb.Style(foreground=c)
+ gdb.write(str, style=s)
+
+ gdb.write("\n")
+
+
ColorTester()
StyleTester()
+StyleTester2()
diff --git a/gdb/testsuite/gdb.python/py-style.exp b/gdb/testsuite/gdb.python/py-style.exp
index 020d0e0319b..7f3b01333a1 100644
--- a/gdb/testsuite/gdb.python/py-style.exp
+++ b/gdb/testsuite/gdb.python/py-style.exp
@@ -340,3 +340,32 @@ gdb_test_no_output "python input_text = \"a totally different string that is als
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"
+
+# Test gdb.write passing in a style. Define a helper function to
+# ensure all output is flushed before we return to the prompt.
+gdb_test_multiline "create function to call gdb.write then flush" \
+ "python" "" \
+ "def write_and_flush(*args, **kwargs):" "" \
+ " gdb.write(*args, **kwargs)" "" \
+ " gdb.write(\"\\n\")" "" \
+ " gdb.flush(gdb.STDOUT)" "" \
+ "end" ""
+
+gdb_test "python write_and_flush(\"some text\")" \
+ "^some text" "unstyled text, no style passed"
+
+gdb_test "python write_and_flush(\"some text\", style=None)" \
+ "^some text" "unstyled text, pass style as None"
+
+gdb_test "python write_and_flush(\"some text\", style=filename_style)" \
+ "^\033\\\[34;41;2;27msome text\033\\\[m" \
+ "styled output, pass style by keyword"
+
+gdb_test "python write_and_flush(\"some text\", gdb.STDOUT, filename_style)" \
+ "^\033\\\[34;41;2;27msome text\033\\\[m" \
+ "styled output, pass style by position"
+
+gdb_test "python write_and_flush(\"some text\", style='filename')" \
+ [multi_line \
+ "Python Exception <class 'TypeError'>: 'style' argument must be gdb\\.Style or None, not str\\." \
+ "Error occurred in Python: 'style' argument must be gdb\\.Style or None, not str\\."]
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv5 0/4] Python Style API
2025-06-18 19:30 ` [PATCHv4 0/4] " Andrew Burgess
` (3 preceding siblings ...)
2025-06-18 19:30 ` [PATCHv4 4/4] gdb: extend gdb.write to support styled output Andrew Burgess
@ 2025-06-25 14:42 ` Andrew Burgess
2025-06-25 14:42 ` [PATCHv5 1/4] gdb: allow gdb.Color to work correctly with pagination Andrew Burgess
` (4 more replies)
4 siblings, 5 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-06-25 14:42 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
After the gdb.Color support was added to the Python API I wondered if
this would be enough to easily add styling to custom Python commands.
But I don't think it is, there's still lots that would need to be
handled in user code, for example, the intensity setting. And having
styles that track named styles (e.g. if I want to apply the 'filename'
style to some text from Python).
So this series builds on top of the gdb.Color API to provide a new
gdb.Style API which, I hope, makes it easy to start writing Python
commands that can use GDB's styles.
Here's an example of a simple custome command that uses styles:
import os
class user_home_cmd(gdb.Command):
def __init__(self):
super().__init__("user-home", gdb.COMMAND_USER)
self._style = gdb.Style('filename')
def invoke(self, args, from_tty):
print("The filename is %s."
% (self._style.apply(os.environ['HOME'])))
user_home_cmd()
The new 'user-home' command prints the current users home directory
using 'filename' style. Changing the 'filename' style and re-running
the command will track the style changes.
In v2:
- Fixed an issue with the py-style-parameter-set.exp test; GDB was
not started in a terminal with styling support in one case, so
tests would fail depending on how the testsuite was being run.
In v3:
- Another test issue. This time, the error message given when
parsing an argument of the wrong type changes with Python version.
Relax the regexp to accept any 'TypeError' message.
In v4:
- Two new patches. First patch extends pager_file so that
gdb.Color, and gdb.Style when it's added can work well with GDB's
pager. Last patch extends the gdb.write() Python function to also
support styling.
- I've renamed some of the classes in python/py-style.c, nothing
major, I replaced 'stylepy' with 'style' in some names. I think
the new names are clearer, and better match the rest of GDB's
Python API code.
- Unfortunately, the new pager_file changes mean that this patch
series now depends on this other patch:
https://inbox.sourceware.org/gdb-patches/444008aeae2bb3c68cf868fa317374b3d7973860.1750197766.git.aburgess@redhat.com
Without that patch there will be some failures in
gdb.python/py-color-pagination.exp. I'm not proposing to merge
this until that other patch, or something like it, is merged. But
this series could be reviewed independently.
In v5:
- The pager_file styling fixes mentioned for v4 have now been merged
to master. I've rebased this series on top of them, and all the
tests are now passing.
- I've tweaked the wording for a couple of the commit messages just
to make things clearer.
- No real code changes.
Thanks,
Andrew
---
Andrew Burgess (4):
gdb: allow gdb.Color to work correctly with pagination
gdb/python: add gdb.Style class
gdb/python: new class gdb.StyleParameterSet
gdb/python: extend gdb.write to support styled output
gdb/Makefile.in | 1 +
gdb/NEWS | 14 +
gdb/doc/python.texi | 262 +++++-
gdb/python/lib/gdb/__init__.py | 209 +++++
gdb/python/py-style.c | 824 ++++++++++++++++++
gdb/python/python-internal.h | 15 +
gdb/python/python.c | 46 +-
.../gdb.python/py-color-pagination.exp | 166 ++++
.../gdb.python/py-color-pagination.py | 82 ++
.../gdb.python/py-style-parameter-set.exp | 366 ++++++++
gdb/testsuite/gdb.python/py-style.exp | 371 ++++++++
gdb/ui-style.h | 6 +
gdb/utils.c | 21 +-
13 files changed, 2366 insertions(+), 17 deletions(-)
create mode 100644 gdb/python/py-style.c
create mode 100644 gdb/testsuite/gdb.python/py-color-pagination.exp
create mode 100644 gdb/testsuite/gdb.python/py-color-pagination.py
create mode 100644 gdb/testsuite/gdb.python/py-style-parameter-set.exp
create mode 100644 gdb/testsuite/gdb.python/py-style.exp
base-commit: 125881849ad75f05d6c35fdb02a290cb740a75d4
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv5 1/4] gdb: allow gdb.Color to work correctly with pagination
2025-06-25 14:42 ` [PATCHv5 0/4] Python Style API Andrew Burgess
@ 2025-06-25 14:42 ` Andrew Burgess
2025-06-25 14:42 ` [PATCHv5 2/4] gdb/python: add gdb.Style class Andrew Burgess
` (3 subsequent siblings)
4 siblings, 0 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-06-25 14:42 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
This commit allows gdb.Color objects to be used to style output from
GDB commands written in Python, and the styled output should work
correctly with pagination.
There are two parts to fixing this:
First, GDB needs to be able to track the currently applied style
within the page_file class. This means that style changes need to be
achieved with calls to pager_file::emit_style_escape.
Now usually, GDB does this by calling something like fprintf_styled,
which takes care to apply the style for us. However, that's not
really an option here as a gdb.Color isn't a full style, and as the
gdb.Color object is designed to be converted directly into escape
sequences that can then be printed, we really need a solution that
works with this approach.
However pager_file::puts already has code in place to handle escape
sequences. Right now all this code does is spot the escape sequence
and append it to the m_wrap_buffer. But in this commit I propose that
we go one step further, parse the escape sequence back into a
ui_file_style object in pager_file::puts, and then we can call
pager_file::emit_style_escape.
If the parsing doesn't work then we can just add the escape sequence
to m_wrap_buffer as we did before.
But wait, how can this work if a gdb.Color isn't a full style? Turns
out that's not a problem. We only ever emit the escape sequence for
those parts of a style that need changing, so a full style that sets
the foreground color will emit the same escape sequence as a gdb.Color
for the foreground. When we convert the escape sequence back into a
ui_file_style, then we get a style with everything set to default,
except the foreground color.
I had hoped that this would be all that was needed. But unfortunately
this doesn't work because of the second problem...
... the implementation of the Python function gdb.write() calls
gdb_printf(), which calls gdb_vprintf(), which calls ui_file::vprintf,
which calls ui_out::vmessage, which calls ui_out::call_do_message, and
finally we reach cli_ui_out::do_message. This final do_message
function does this:
ui_file *stream = m_streams.back ();
stream->emit_style_escape (style);
stream->puts (str.c_str ());
stream->emit_style_escape (ui_file_style ());
If we imagine the case where we are emitting a style, triggered from
Python like this:
gdb.write(gdb.Color('red').escape_sequence(True))
the STYLE in this case will be the default ui_file_style(), and STR
will hold the escape sequence we are writing.
After the first change, where pager_file::puts now calls
pager_file::emit_style_escape, the current style of STREAM will have
been updated. But this means that the final emit_style_escape will
now restore the default style.
The fix for this is to avoid using the high level gdb_printf from
gdb.write(), and instead use gdb_puts instead. The gdb_puts function
doesn't restore the default style, which means our style modification
survives.
There's a new test included. This test includes what appears like a
pointless extra loop (looping over a single value), but later commits
in this series will add more values to this list.
---
gdb/python/python.c | 18 +--
| 116 ++++++++++++++++++
| 46 +++++++
gdb/utils.c | 21 +++-
4 files changed, 189 insertions(+), 12 deletions(-)
create mode 100644 gdb/testsuite/gdb.python/py-color-pagination.exp
create mode 100644 gdb/testsuite/gdb.python/py-color-pagination.py
diff --git a/gdb/python/python.c b/gdb/python/python.c
index ff50c424ad9..dddf5c39c58 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -1570,21 +1570,21 @@ gdbpy_write (PyObject *self, PyObject *args, PyObject *kw)
try
{
+ ui_file *stream;
switch (stream_type)
{
case 1:
- {
- gdb_printf (gdb_stderr, "%s", arg);
- break;
- }
+ stream = gdb_stderr;
+ break;
case 2:
- {
- gdb_printf (gdb_stdlog, "%s", arg);
- break;
- }
+ stream = gdb_stdlog;
+ break;
default:
- gdb_printf (gdb_stdout, "%s", arg);
+ stream = gdb_stdout;
+ break;
}
+
+ gdb_puts (arg, stream);
}
catch (const gdb_exception &except)
{
--git a/gdb/testsuite/gdb.python/py-color-pagination.exp b/gdb/testsuite/gdb.python/py-color-pagination.exp
new file mode 100644
index 00000000000..cb410da2274
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-color-pagination.exp
@@ -0,0 +1,116 @@
+# 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.Color and how this
+# interacts with GDB's pagination system.
+
+load_lib gdb-python.exp
+
+require allow_python_tests
+
+standard_testfile
+
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+
+set str "<[string repeat - 78]>"
+
+set black "(?:\033\\\[30;49;22;27m)"
+set red "(?:\033\\\[31;49;22;27m)"
+set green "(?:\033\\\[32;49;22;27m)"
+set yellow "(?:\033\\\[33;49;22;27m)"
+set blue "(?:\033\\\[34;49;22;27m)"
+set magenta "(?:\033\\\[35;49;22;27m)"
+set cyan "(?:\033\\\[36;49;22;27m)"
+set white "(?:\033\\\[37;49;22;27m)"
+
+set any_color "(?:${black}|${red}|${green}|${yellow}|${blue}|${magenta}|${cyan}|${white})"
+
+# Run the command 'TYPE-fill MODE' which fills the screen with output and
+# triggers the pagination prompt. Check that styling is applied correctly
+# to the output.
+proc test_pagination { type mode } {
+
+ # Start with a fresh GDB, but enable color support.
+ with_ansi_styling_terminal {
+ clean_restart
+ }
+
+ gdb_test_no_output "source $::pyfile" "source the script"
+
+ gdb_test_no_output "set width 80"
+ gdb_test_no_output "set height 15"
+
+ set saw_bad_color_handling false
+ set expected_restore_color ""
+ set last_color ""
+ gdb_test_multiple "$type-fill $mode" "" {
+ -re "^$type-fill $mode\r\n" {
+ exp_continue
+ }
+
+ -re "^(${::any_color}?)(${::any_color})$::str" {
+ # After a continuation prompt GDB will restore the previous
+ # color, and then we immediately switch to a new color.
+ set restored_color $expect_out(1,string)
+ if { $restored_color ne ""
+ && $restored_color ne $expected_restore_color } {
+ set saw_bad_color_handling true
+ }
+ set last_color $expect_out(2,string)
+ exp_continue
+ }
+
+ -re "^\033\\\[${::decimal}m$::str" {
+ # This catches the case where the color's escape sequence has
+ # not been converted back into a full style. This indicates
+ # something went wrong in the pager_file::puts function.
+ set saw_bad_color_handling true
+ exp_continue
+ }
+
+ -re "^((?:\033\\\[m)?)$::pagination_prompt$" {
+ # After a pagination prompt we expect GDB to restore the last
+ # color.
+ set expected_restore_color $last_color
+
+ # If we didn't see a color reset sequence then the pagination
+ # prompt will have been printed in the wrong color, this is a
+ # GDB bug.
+ set color_reset $expect_out(1,string)
+ if { $color_reset eq "" } {
+ set saw_bad_color_handling true
+ }
+
+ # Send '\n' to view more output.
+ send_gdb "\n"
+ exp_continue
+ }
+
+ -re "^\r\n" {
+ # The matches the newline sent to the continuation prompt.
+ exp_continue
+ }
+
+ -re "^\033\\\[m\r\n$::gdb_prompt $" {
+ gdb_assert { !$saw_bad_color_handling } $gdb_test_name
+ }
+ }
+}
+
+foreach_with_prefix type { color } {
+ foreach_with_prefix mode { write print } {
+ test_pagination $type $mode
+ }
+}
--git a/gdb/testsuite/gdb.python/py-color-pagination.py b/gdb/testsuite/gdb.python/py-color-pagination.py
new file mode 100644
index 00000000000..efd501eedf5
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-color-pagination.py
@@ -0,0 +1,46 @@
+# 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/>.
+
+import gdb
+
+basic_colors = ["black", "red", "green", "yellow", "blue", "magenta", "cyan", "white"]
+
+
+def write(mode, text):
+ if mode == "write":
+ gdb.write(text)
+ else:
+ print(text, end="")
+
+
+class ColorTester(gdb.Command):
+ def __init__(self):
+ super().__init__("color-fill", gdb.COMMAND_USER)
+
+ def invoke(self, args, from_tty):
+ mode = args
+ str = "<" + "-" * 78 + ">"
+ for i in range(0, 20):
+ for color_name in basic_colors:
+ c = gdb.Color(color_name)
+ write(mode, c.escape_sequence(True))
+ write(mode, str)
+
+ default = gdb.Color("none")
+ write(mode, default.escape_sequence(True))
+ write(mode, "\n")
+
+
+ColorTester()
diff --git a/gdb/utils.c b/gdb/utils.c
index 6ae362c5400..d4407d43163 100644
--- a/gdb/utils.c
+++ b/gdb/utils.c
@@ -1702,10 +1702,25 @@ pager_file::puts (const char *linebuffer)
else if (*linebuffer == '\033'
&& skip_ansi_escape (linebuffer, &skip_bytes))
{
- m_wrap_buffer.append (linebuffer, skip_bytes);
- /* Note that we don't consider this a character, so we
+ /* We don't consider escape sequences as characters, so we
don't increment chars_printed here. */
- linebuffer += skip_bytes;
+
+ size_t style_len;
+ ui_file_style style;
+ if (style.parse (linebuffer, &style_len)
+ && style_len <= skip_bytes)
+ {
+ this->emit_style_escape (style);
+
+ linebuffer += style_len;
+ skip_bytes -= style_len;
+ }
+
+ if (skip_bytes > 0)
+ {
+ m_wrap_buffer.append (linebuffer, skip_bytes);
+ linebuffer += skip_bytes;
+ }
}
else if (*linebuffer == '\r')
{
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv5 2/4] gdb/python: add gdb.Style class
2025-06-25 14:42 ` [PATCHv5 0/4] Python Style API Andrew Burgess
2025-06-25 14:42 ` [PATCHv5 1/4] gdb: allow gdb.Color to work correctly with pagination Andrew Burgess
@ 2025-06-25 14:42 ` Andrew Burgess
2025-06-25 14:42 ` [PATCHv5 3/4] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
` (2 subsequent siblings)
4 siblings, 0 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-06-25 14:42 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess, Eli Zaretskii
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 ++++++++++++++++++
| 5 +-
| 20 +
gdb/testsuite/gdb.python/py-style.exp | 342 ++++++++
gdb/ui-style.h | 6 +
8 files changed, 1316 insertions(+), 2 deletions(-)
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 6c8a008d39d..d64e75fd547 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 6fa22851f6a..0b4ed9d6a1c 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.
@@ -7262,6 +7263,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..b3983af3a85
--- /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 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;
+
+ 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)
+{
+ 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;
+ (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)
+{
+ 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);
--git a/gdb/testsuite/gdb.python/py-color-pagination.exp b/gdb/testsuite/gdb.python/py-color-pagination.exp
index cb410da2274..7b3dc1859c1 100644
--- a/gdb/testsuite/gdb.python/py-color-pagination.exp
+++ b/gdb/testsuite/gdb.python/py-color-pagination.exp
@@ -14,7 +14,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# This file is part of the GDB testsuite. It tests gdb.Color and how this
-# interacts with GDB's pagination system.
+# interacts with GDB's pagination system. The test also tests gdb.Style
+# because the tests are very similar.
load_lib gdb-python.exp
@@ -109,7 +110,7 @@ proc test_pagination { type mode } {
}
}
-foreach_with_prefix type { color } {
+foreach_with_prefix type { color style } {
foreach_with_prefix mode { write print } {
test_pagination $type $mode
}
--git a/gdb/testsuite/gdb.python/py-color-pagination.py b/gdb/testsuite/gdb.python/py-color-pagination.py
index efd501eedf5..f0252e5bf86 100644
--- a/gdb/testsuite/gdb.python/py-color-pagination.py
+++ b/gdb/testsuite/gdb.python/py-color-pagination.py
@@ -43,4 +43,24 @@ class ColorTester(gdb.Command):
write(mode, "\n")
+class StyleTester(gdb.Command):
+ def __init__(self):
+ super().__init__("style-fill", gdb.COMMAND_USER)
+
+ def invoke(self, args, from_tty):
+ mode = args
+ str = "<" + "-" * 78 + ">"
+ for i in range(0, 20):
+ for color_name in basic_colors:
+ c = gdb.Color(color_name)
+ s = gdb.Style(foreground=c)
+ write(mode, s.escape_sequence())
+ write(mode, str)
+
+ default = gdb.Style()
+ write(mode, default.escape_sequence())
+ write(mode, "\n")
+
+
ColorTester()
+StyleTester()
diff --git a/gdb/testsuite/gdb.python/py-style.exp b/gdb/testsuite/gdb.python/py-style.exp
new file mode 100644
index 00000000000..020d0e0319b
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-style.exp
@@ -0,0 +1,342 @@
+# 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"
+
+# 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"
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
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv5 3/4] gdb/python: new class gdb.StyleParameterSet
2025-06-25 14:42 ` [PATCHv5 0/4] Python Style API Andrew Burgess
2025-06-25 14:42 ` [PATCHv5 1/4] gdb: allow gdb.Color to work correctly with pagination Andrew Burgess
2025-06-25 14:42 ` [PATCHv5 2/4] gdb/python: add gdb.Style class Andrew Burgess
@ 2025-06-25 14:42 ` Andrew Burgess
2025-06-25 14:42 ` [PATCHv5 4/4] gdb/python: extend gdb.write to support styled output Andrew Burgess
2025-07-07 14:43 ` [PATCHv6 0/4] Python Style API Andrew Burgess
4 siblings, 0 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-06-25 14:42 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
Add a new helper class gdb.StyleParameterSet. This new class can be
used to simplify creation of new style parameter sets. A style
parameter set is the 'foreground', 'background', and (optionally), the
'intensity' settings, all grouped under a single prefix command.
And example usage is:
(gdb) python s = gdb.StyleParameterSet("my-style")
(gdb) show style my-style
style my-style background: The "my-style" style background color is: none
style my-style foreground: The "my-style" style foreground color is: none
style my-style intensity: The "my-style" style display intensity is: normal
(gdb)
Having created a gdb.StyleParameterSet, the object itself can be used
to access a named style corresponding to the setting group, like this:
(gdb) python print(s.style)
<gdb.Style name='my-style', fg=none, bg=none, intensity=normal>
(gdb)
Of course, having access to the gdb.Style makes it easy to change the
settings, or the settings can be adjusted via the normal CLI 'set'
commands.
As gdb.StyleParameterSet manages a set of parameters, and the
gdb.Parameter class uses Parameter.value as the attribute to read the
parameter's value, there is also StyleParameterSet.value, but this is
just an alias for StyleParameterSet.style, that is, it allows the
gdb.Style object to be read and written too.
It is worth noting that this class only creates a single level of
prefix command. As an example GDB has style 'disassembler mnemonic',
where the 'disassembler' part is a group of related styles. If a user
wanted to create:
style
my-style-group
style-1
style-2
style-3
Where each of 'style-1', 'style-2', and 'style-3' will have the full
set of 'foreground', 'background', and 'intensity', then the
gdb.StyleParameterSet can be used to create the 'style-N' part, but
the user will have to create the 'my-style-group' prefix themselves,
possibly using gdb.ParameterPrefix, e.g.:
gdb.ParameterPrefix("style my-style-group", gdb.COMMAND_NONE)
gdb.StyleParameterSet("my-style-group style-1")
gdb.StyleParameterSet("my-style-group style-2")
gdb.StyleParameterSet("my-style-group style-3")
---
gdb/NEWS | 4 +
gdb/doc/python.texi | 115 +++++-
gdb/python/lib/gdb/__init__.py | 209 ++++++++++
.../gdb.python/py-style-parameter-set.exp | 366 ++++++++++++++++++
4 files changed, 693 insertions(+), 1 deletion(-)
create mode 100644 gdb/testsuite/gdb.python/py-style-parameter-set.exp
diff --git a/gdb/NEWS b/gdb/NEWS
index d64e75fd547..4ae57ca776f 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -138,6 +138,10 @@ info threads [-gid] [-stopped] [-running] [ID]...
gdb.INTENSITY_DIM for use with gdb.Style when representing
intensities.
+ ** New gdb.StyleParameterSet for creating custom style settings.
+ Use gdb.StyleParameterSet(NAME) to create 'set style NAME ...'
+ and 'show style NAME ...' parameters.
+
** 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 0b4ed9d6a1c..486f0a8f750 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -5293,6 +5293,7 @@ Parameters In Python
feature-2}, then the @kbd{plugin-name} would need to be a prefix
command (@pxref{CLI Commands In Python}).
+@anchor{gdb.ParameterPrefix}
However, when creating parameters, you will almost always need to
create two prefix commands, one as a @kbd{set} sub-command, and one as
a @kbd{show} sub-command. @value{GDBN} provides the
@@ -5379,6 +5380,117 @@ Parameters In Python
ExampleParam("plugin-name feature-2")
@end smallexample
+@anchor{Creating Style Parameters}
+The helper class @code{gdb.StyleParameterSet} exists to make it easier
+to create new styles. @value{GDBN} places style settings under
+@samp{show style @dots{}} and @samp{set style @dots{}}, an example of a style
+is @samp{filename}. Each style is really a prefix command (@pxref{CLI
+Commands In Python}), with sub-commands @samp{foreground},
+@samp{background}, and optionally, @samp{intensity}.
+
+It is simple enough to create a new style using two @code{gdb.Command}
+objects for the prefix commands (one for @samp{set}, and one for
+@samp{show}), and three @code{gdb.Parameter} objects, one each for the
+@samp{foreground}, @samp{background}, and @samp{intensity}. You would
+also want to take care to craft the help text so that the new style
+behaves the same as the existing styles.
+
+Or, you can use the @code{gdb.StyleParameterSet} class, which takes
+care of all this, as the following example shows:
+
+@smallexample
+@group
+(@value{GDBP}) python s = gdb.StyleParameterSet("my-style")
+(@value{GDBP}) show style my-style
+style my-style background: The "my-style" style background color is: none
+style my-style foreground: The "my-style" style foreground color is: none
+style my-style intensity: The "my-style" style display intensity is: normal
+(@value{GDBP})
+@end group
+@end smallexample
+
+You might also want to group a number of styles within a new prefix,
+similar to how @value{GDBN} groups disassembler related styles within
+the @samp{style disassembler} prefix. This can be done using
+@code{gdb.ParameterPrefix} (@pxref{gdb.ParameterPrefix}), as in this
+example:
+
+@smallexample
+@group
+(@value{GDBP}) python gdb.ParameterPrefix("style my-group", gdb.COMMAND_NONE)
+(@value{GDBP}) python s_a = gdb.StyleParameterSet("my-group aaa")
+(@value{GDBP}) python s_b = gdb.StyleParameterSet("my-group bbb")
+(@value{GDBP}) show style my-group
+style my-group aaa background: The "my-group aaa" style background color is: none
+style my-group aaa foreground: The "my-group aaa" style foreground color is: none
+style my-group aaa intensity: The "my-group aaa" style display intensity is: normal
+style my-group bbb background: The "my-group bbb" style background color is: none
+style my-group bbb foreground: The "my-group bbb" style foreground color is: none
+style my-group bbb intensity: The "my-group bbb" style display intensity is: normal
+(@value{GDBP})
+@end group
+@end smallexample
+
+The @code{gdb.StyleParameterSet} class has the following methods and
+attributes:
+
+@defun StyleParameterSet.__init__(name, @w{add_intensity=@code{True}}, @w{doc=@code{None}})
+Create a new style group based on @var{name}, which is a string. For
+example if @var{name} is @samp{my-style}, then @value{GDBN} will
+create the prefix commands @samp{set style my-style} and @samp{show
+style my-style}. Within these prefix commands will be
+@samp{foreground}, @samp{background}, and @samp{intensity} parameters
+with the appropriate types.
+
+It is also possible for @var{name} to consist of multiple words, so
+long as each prefix command (except the last one) already exists. For
+example, it is valid to use a @var{name} value of @samp{disassembler
+my-style}, as the @samp{disassembler} prefix command already exists.
+@value{GDBN} would then create @samp{set style disassembler my-style}
+and @samp{show style disassembler my-style}, and within the
+@samp{my-style} prefixes will be the @samp{foreground},
+@samp{background}, and @samp{intensity} parameters with the
+appropriate types.
+
+Every style requires a @samp{foreground} and @samp{background}, but
+not every style needs an @samp{intensity}. If @var{add_intensity} is
+@code{True} (the default), then the @samp{intensity} parameter will be
+created. If @var{add_intensity} is @code{False}, then the
+@samp{intensity} parameter will not be created.
+
+If the @samp{intensity} parameter is not created, then the
+@code{gdb.Style} (@pxref{Styles In Python}) created from this
+@code{gdb.StyleParameterSet} will have @code{gdb.INTENSITY_NORMAL}.
+
+The @var{doc} should be a string which will be used as the help text
+for the @var{name} prefix command. This text is used as the
+@code{Command.__doc__} value for the @code{gdb.Command} object that is
+the prefix command object (@pxref{CLI Commands In Python}). If
+@var{doc} is @code{None} (the default) then a basic default help text
+is used.
+@end defun
+
+@defun StyleParameterSet.apply(string)
+Equivalent to @code{StyleParameterSet.style.apply(string)}. Returns a
+copy of @var{string} with escape sequences added to the start and end.
+The escape sequence at the start applies this style, and the escape
+sequence at the end restores the terminal default.
+
+If styling is disabled (i.e.@: @samp{set style enabled off}), then no
+escape sequences are added and this method returns a copy of
+@var{string}.
+@end defun
+
+@defvar StyleParameterSet.style
+This read/write attribute holds a @code{gdb.Style} object
+(@pxref{Styles In Python}), that is a named style associated with this
+style parameter group.
+@end defvar
+
+@defvar StyleParameterSet.value
+This is an alias for @code{StyleParameterSet.style}, see above.
+@end defvar
+
@node Functions In Python
@subsubsection Writing new convenience functions
@@ -7275,7 +7387,8 @@ Styles In Python
@value{GDBN} has many styles builtin (@pxref{Output Styling}), and
style objects can be created that apply these builtin styles to Python
-output.
+output. It is also possible to create new styles which can be used to
+style Python output (@pxref{Creating Style Parameters}).
The style class is called @code{gdb.Style}, and has the following
methods and attributes:
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index cedd897ab0f..f2f57280007 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -515,3 +515,212 @@ class ParameterPrefix:
def dont_repeat(self):
if self.active_prefix is not None:
self.active_prefix.dont_repeat()
+
+
+class StyleParameterSet:
+ """Create new style parameters.
+
+ A style parameter is a set of parameters that start with 'set style ...'
+ and 'show style ...'. For example 'filename' is a style parameter, and
+ 'disassembler symbol' is another style parameter.
+
+ The name of the style parameter is really a prefix command. Under this
+ we must have two commands 'foreground' and 'background', which are color
+ parameters. A third, optional command 'intensity', is an enum with
+ values 'normal', 'bold', and 'dim'.
+
+ A StyleParameterSet is initialised with a name, e.g. 'filename' or
+ 'disassembler symbol'. The StyleParameterSet creates the prefix
+ commands in the 'set style' and 'show style' name space, and then adds
+ the 'foreground', 'background', and optionally, the 'intensity'
+ commands.
+
+ If you want a whole new style group, similar to 'disassembler',
+ then you need to add this yourself first, then StyleParameterSet
+ can be used to create styles within the new prefix group.
+
+ The 'value' attribute on this object can be used to get and set a
+ gdb.Style object which controls all aspects of this style.
+
+ For readability, the alias 'style' is the same as 'value'.
+ """
+
+ def __init__(self, name, add_intensity=True, doc=None):
+ # The STYLE_NAME is something like 'filename' is 'set style
+ # filename ...', and PARAM_NAME is one of 'foreground',
+ # 'background', or 'intensity'. The DESC_TEXT is the long
+ # form used in help text, like 'foreground color' or 'display
+ # intensity'. The DEFAULT_VALUE is used to set the SELF.value
+ # attribute. And PARAM_TYPE is a gdb.PARAM_* constant. The
+ # ARGS is used for gdb.PARAM_ENUM, which ARGS should be the
+ # enum value list.
+ class style_parameter(Parameter):
+ def __init__(
+ self,
+ style_name,
+ parent_obj,
+ param_name,
+ desc_text,
+ default_value,
+ param_type,
+ *args
+ ):
+ # Setup documentation must be done before calling
+ # parent's __init__ method, as the __init__ reads (and
+ # copies) these values.
+ self.show_doc = "Show the " + desc_text + " for this property."
+ self.set_doc = "Set the " + desc_text + " for this property."
+ self.__doc__ = ""
+
+ # Call the parent's __init__ method to actually create
+ # the parameter.
+ super().__init__(
+ "style " + style_name + " " + param_name,
+ COMMAND_NONE,
+ param_type,
+ *args
+ )
+
+ # Store information we need in other methods.
+ self._style_name = style_name
+ self._desc_text = desc_text
+ self._parent = parent_obj
+
+ # Finally, setup the default value.
+ self.value = default_value
+
+ # Return the 'show style <style-name> <attribute>' string,
+ # which has styling applied.
+ def get_show_string(self, value):
+ s = self._parent.style
+ return (
+ "The "
+ + s.apply('"' + self._style_name + '" style')
+ + " "
+ + self._desc_text
+ + " is: "
+ + value
+ )
+
+ class style_foreground_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "foreground",
+ "foreground color",
+ Color(),
+ PARAM_COLOR,
+ )
+
+ class style_background_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "background",
+ "background color",
+ Color(),
+ PARAM_COLOR,
+ )
+
+ class style_intensity_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "intensity",
+ "display intensity",
+ "normal",
+ PARAM_ENUM,
+ ["normal", "bold", "dim"],
+ )
+
+ if doc is None:
+ doc = (
+ "The "
+ + name
+ + " display styling.\nConfigure "
+ + name
+ + " colors and display intensity."
+ )
+
+ ParameterPrefix("style " + name, COMMAND_NONE, doc)
+ self._foreground = style_foreground_parameter(name, self)
+ self._background = style_background_parameter(name, self)
+ if add_intensity:
+ self._intensity = style_intensity_parameter(name, self)
+ self._name = name
+ self._style = None
+
+ @property
+ def value(self):
+ """Return the gdb.Style object for this parameter set."""
+ if self._style is None:
+ self._style = Style(self._name)
+ return self._style
+
+ @property
+ def style(self):
+ """Return the gdb.Style object for this parameter set.
+
+ This is an alias for self.value."""
+ return self.value
+
+ @value.setter
+ def value(self, new_value):
+ """Set this parameter set to NEW_VALUE, a gdb.Style object.
+
+ The attributes of NEW_VALUE are used to update the current settings
+ of this parameter set. If this parameter set was created without
+ an intensity setting, then the intensity of NEW_VALUE is ignored."""
+ if not isinstance(new_value, Style):
+ raise TypeError("value must be gdb.Style, not %s" % type(new_value))
+ self._foreground.value = new_value.foreground
+ self._background.value = new_value.background
+ if hasattr(self, "_intensity"):
+ intensity_value = new_value.intensity
+ if intensity_value == INTENSITY_BOLD:
+ intensity_string = "bold"
+ elif intensity_value == INTENSITY_DIM:
+ intensity_string = "dim"
+ elif intensity_value == INTENSITY_NORMAL:
+ intensity_string = "normal"
+ else:
+ raise ValueError(
+ "unknown intensity value %d from Style" % intensity_value
+ )
+
+ self._intensity.value = intensity_string
+
+ @style.setter
+ def style(self, new_value):
+ """Set this parameter set to NEW_VALUE, a gdb.Style object.
+
+ This is an alias for self.value."""
+ self.value = new_value
+
+ def apply(self, *args, **kwargs):
+ """Apply this style to the arguments.
+
+ Forwards all arguments to self.style.apply(). The arguments should be a string,
+ to which this style is applied. This function returns the same string with
+ escape sequences added to apply this style.
+
+ If styling is globally disabled ('set style enabled off') then no escape sequences
+ will be addedded, the input string is returned."""
+ return self.style.apply(*args, **kwargs)
+
+ def __repr__(self):
+ """A string representation of SELF."""
+
+ def full_typename(obj):
+ module = type(obj).__module__
+ qualname = type(obj).__qualname__
+
+ if module is None or module == "builtins":
+ return qualname
+ else:
+ return module + "." + qualname
+
+ return "<" + full_typename(self) + " name='" + self._name + "'>"
diff --git a/gdb/testsuite/gdb.python/py-style-parameter-set.exp b/gdb/testsuite/gdb.python/py-style-parameter-set.exp
new file mode 100644
index 00000000000..9e05acb84cc
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-style-parameter-set.exp
@@ -0,0 +1,366 @@
+# 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.StyleParameterSet.
+
+load_lib gdb-python.exp
+
+require allow_python_tests
+
+# Create a regexp that can be used to match the output of a 'show style
+# NAME' command. HAS_INTENSITY is a boolean and indicates if style NAME has
+# an intensity attribute.
+proc gen_show_style_re { name has_intensity } {
+ set output \
+ [list \
+ "style ${name} background: The \"${name}\" style background color is: none" \
+ "style ${name} foreground: The \"${name}\" style foreground color is: none"]
+
+ if { $has_intensity } {
+ lappend output \
+ "style ${name} intensity: The \"${name}\" style display intensity is: normal"
+ }
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'set style NAME' command,
+# that is, a 'set' command that doesn't actually set an attribute of a
+# style, in this case GDB will print some useful help text. HAS_INTENSITY is
+# a boolean and indicates if style NAME has an intensity attribute or not.
+proc gen_set_style_re { name has_intensity } {
+ set output \
+ [list \
+ "List of \"set style $name\" subcommands:" \
+ "" \
+ "set style $name background -- Set the background color for this property\\." \
+ "set style $name foreground -- Set the foreground color for this property\\."]
+
+ if { $has_intensity } {
+ lappend output \
+ "set style $name intensity -- Set the display intensity for this property\\."
+ }
+
+ lappend output \
+ "" \
+ "Type \"help set style $name\" followed by subcommand name for full documentation\\." \
+ "Type \"apropos word\" to search for commands related to \"word\"\\." \
+ "Type \"apropos -v word\" for full documentation of commands related to \"word\"\\." \
+ "Command name abbreviations are allowed if unambiguous\\."
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'help show style NAME'
+# command. HAS_INTENSITY is a boolean and indicates if style NAME has an
+# intensity attribute or not. DOC is a regexp that matches the doc string
+# for style NAME.
+proc gen_help_show_style_re { name has_intensity doc } {
+ set output \
+ [list \
+ $doc \
+ "" \
+ "List of \"show style ${name}\" subcommands:" \
+ "" \
+ "show style $name background -- Show the background color for this property\\." \
+ "show style $name foreground -- Show the foreground color for this property\\."]
+ if { $has_intensity } {
+ lappend output \
+ "show style $name intensity -- Show the display intensity for this property\\."
+ }
+
+ lappend output \
+ "" \
+ "Type \"help show style $name\" followed by subcommand name for full documentation\\." \
+ "Type \"apropos word\" to search for commands related to \"word\"\\." \
+ "Type \"apropos -v word\" for full documentation of commands related to \"word\"\\." \
+ "Command name abbreviations are allowed if unambiguous\\."
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'help set style NAME'
+# command. HAS_INTENSITY is a boolean and indicates if style NAME has an
+# intensity attribute or not. DOC is a regexp that matches the doc string
+# for style NAME.
+proc gen_help_set_style_re { name has_intensity doc } {
+ return \
+ [multi_line \
+ $doc \
+ "" \
+ [gen_set_style_re $name $has_intensity]]
+}
+
+# Create styles with and without intensity. Use named and unnamed
+# argument passing, and vary the argument passing order. Create
+# styles with and without documentation.
+#
+# Confirm that the styles contain the expected sub-commands, and that
+# the documentation is as expected.
+proc_with_prefix test_basic_usage {} {
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"style-1\")" \
+ "create style-1"
+
+ gdb_test_no_output \
+ "python style_2 = gdb.StyleParameterSet(add_intensity=True, name=\"style-2\")" \
+ "create style-2"
+
+ gdb_test_no_output \
+ "python style_3 = gdb.StyleParameterSet(name=\"style-3\", doc=\"Style-3 display styling.\\nThis is a multi-line documentation\\nstring describing style-3.\")" \
+ "create style-3"
+
+ gdb_test_no_output \
+ "python style_4 = gdb.StyleParameterSet(\"style-4\", add_intensity=False)" \
+ "create style-4"
+
+ foreach style { style-1 style-2 style-3 } {
+ gdb_test "show style $style" \
+ [gen_show_style_re $style true]
+ gdb_test "set style $style" \
+ [gen_set_style_re $style true]
+ }
+
+ gdb_test "show style style-4" \
+ [gen_show_style_re "style-4" false]
+
+ foreach style { style-1 style-2 } {
+ gdb_test "help show style $style" \
+ [gen_help_show_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+
+
+ set out [gen_help_set_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+
+ gdb_test "help set style $style" \
+ [gen_help_set_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+ }
+
+ gdb_test "help show style style-3" \
+ [gen_help_show_style_re "style-3" true \
+ [multi_line \
+ "Style-3 display styling\\." \
+ "This is a multi-line documentation" \
+ "string describing style-3\\."]]
+
+ gdb_test "help show style style-4" \
+ [gen_help_show_style_re "style-4" false \
+ [multi_line \
+ "The style-4 display styling\\." \
+ "Configure style-4 colors and display intensity\\."]]
+
+ for { set i 1 } { $i < 5 } { incr i } {
+ gdb_test "python print(style_$i)" \
+ "<gdb.StyleParameterSet name='style-$i'>" \
+ "print repr of style_$i"
+ }
+
+ # There is no 'style-4 intensity' property.
+ gdb_test "show style style-4 foreground" \
+ "The \"style-4\" style foreground color is: none"
+ gdb_test "show style style-4 background" \
+ "The \"style-4\" style background color is: none"
+ gdb_test "show style style-4 intensity" \
+ "Undefined show style style-4 command: \"intensity\"\\. Try \"help show style style-4\"\\."
+
+ # There is a 'style-1 intensity' property.
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: none" \
+ "show style-1 foreground before changes"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: none" \
+ "show style-1 background before changes"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: normal" \
+ "show style-1 intensity before changes"
+
+ # Grab the gdb.Style objects from 'style-1'.
+ gdb_test_no_output "python s1 = style_1.style"
+ gdb_test_no_output "python s2 = style_1.value"
+
+ # Check both represent the same style.
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=none, bg=none, intensity=normal>" \
+ "print s1 style before changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=none, bg=none, intensity=normal>" \
+ "print s2 style before changes"
+
+ gdb_test_no_output \
+ "python s1.foreground=gdb.Color('red')" "set foreground"
+ gdb_test_no_output \
+ "python s1.background=gdb.Color('blue')" "set background"
+ gdb_test_no_output \
+ "python s1.intensity=gdb.INTENSITY_DIM" "set intensity"
+
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=red, bg=blue, intensity=dim>" \
+ "print s1 style after first set of changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=red, bg=blue, intensity=dim>" \
+ "print s2 style after first set of changes"
+
+ # Check the style properties have updated.
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: red" \
+ "show style-1 foreground after first set of changes"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: blue" \
+ "show style-1 background after first set of changes"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: dim" \
+ "show style-1 intensity after first set of changes"
+
+ # Change the style properties, check gdb.Style objects update.
+ gdb_test_no_output "set style style-1 foreground yellow"
+ gdb_test_no_output "set style style-1 background cyan"
+ gdb_test_no_output "set style style-1 intensity bold"
+
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=yellow, bg=cyan, intensity=bold>" \
+ "print s1 style after second set of changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=yellow, bg=cyan, intensity=bold>" \
+ "print s2 style after second set of changes"
+
+ # Assign a gdb.Style to set 'style-1'. First create some unnamed
+ # style objects that can be used.
+ gdb_test_no_output \
+ "python uns1 = gdb.Style(foreground=gdb.Color('white'), background=gdb.Color('black'), intensity=gdb.INTENSITY_BOLD)" \
+ "create uns1"
+ gdb_test_no_output \
+ "python uns2 = gdb.Style(foreground=gdb.Color('black'), background=gdb.Color('white'), intensity=gdb.INTENSITY_DIM)" \
+ "create uns2"
+
+ gdb_test_no_output "python style_1.value = uns1"
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: white" \
+ "show style-1 foreground after assigning uns1"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: black" \
+ "show style-1 background after assigning uns1"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: bold" \
+ "show style-1 intensity after assigning uns1"
+
+ gdb_test_no_output "python style_1.style = uns2"
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: black" \
+ "show style-1 foreground after assigning uns2"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: white" \
+ "show style-1 background after assigning uns2"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: dim" \
+ "show style-1 intensity after assigning uns2"
+
+ # Assign a style with an intensity that is not 'NORMAL' to a
+ # StyleParameterSet that doesn't have an intensity. The new
+ # intensity setting should be ignored.
+ gdb_test_no_output "python style_4.style = uns1"
+ gdb_test "show style style-4 foreground" \
+ "The \"style-4\" style foreground color is: white" \
+ "show style-4 foreground after assigning uns1"
+ gdb_test "show style style-4 background" \
+ "The \"style-4\" style background color is: black" \
+ "show style-4 background after assigning uns1"
+ gdb_test "show style style-4 intensity" \
+ "Undefined show style style-4 command: \"intensity\"\\. Try \"help show style style-4\"\\." \
+ "show style-4 intensity after assigning uns1"
+
+ gdb_test "python print(style_4.style)" \
+ "<gdb.Style name='style-4', fg=white, bg=black, intensity=normal>" \
+ "print string repr of style_4's style"
+}
+
+# Test creating a style prefix with gdb.ParameterPrefix, then adding
+# some styles within the new prefix. Change the style through the CLI
+# and confirm that the associated Python object updated as expected.
+proc_with_prefix test_style_prefix {} {
+ gdb_test_no_output \
+ "python gdb.ParameterPrefix(\"style my-style-group\", gdb.COMMAND_NONE)"
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"my-style-group style-1\")" \
+ "create style-1"
+ gdb_test_no_output \
+ "python style_2 = gdb.StyleParameterSet(\"my-style-group style-2\")" \
+ "create style-2"
+
+ gdb_test "python print(style_1.style)" \
+ "<gdb.Style name='my-style-group style-1', fg=none, bg=none, intensity=normal>" \
+ "print 'my-style-group style-1' style before changes"
+ gdb_test "python print(style_2.style)" \
+ "<gdb.Style name='my-style-group style-2', fg=none, bg=none, intensity=normal>" \
+ "print 'my-style-group style-2' style before changes"
+
+ gdb_test_no_output "set style my-style-group style-1 foreground red"
+ gdb_test_no_output "set style my-style-group style-1 background yellow"
+ gdb_test_no_output "set style my-style-group style-1 intensity bold"
+ gdb_test_no_output "set style my-style-group style-2 foreground black"
+ gdb_test_no_output "set style my-style-group style-2 background blue"
+ gdb_test_no_output "set style my-style-group style-2 intensity dim"
+
+ gdb_test "python print(style_1.style)" \
+ "<gdb.Style name='my-style-group style-1', fg=red, bg=yellow, intensity=bold>" \
+ "print 'my-style-group style-1' style after changes"
+ gdb_test "python print(style_2.style)" \
+ "<gdb.Style name='my-style-group style-2', fg=black, bg=blue, intensity=dim>" \
+ "print 'my-style-group style-2' style after changes"
+}
+
+# Test that gdb.StyleParameterSet.apply() works as expected.
+proc_with_prefix test_applying {} {
+ # Create a new StyleParameterSet, and adjust its settings.
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"style-1\")" \
+ "create style-1"
+ gdb_test_no_output \
+ "python uns1 = gdb.Style(foreground=gdb.Color('red'), background=gdb.Color('blue'), intensity=gdb.INTENSITY_BOLD)" \
+ "create uns1"
+ gdb_test_no_output "python style_1 = uns1"
+
+ # When styling is off (which it currently is), no escape sequences
+ # should be added.
+ gdb_test \
+ "python print(style_1.apply('xxx'))" "^xxx" \
+ "apply StyleParameterSet to a string when styling is off"
+
+ # When styling is on, we should see an escape sequence added.
+ gdb_test "with style enabled on -- python print(style_1.apply('xxx'))" \
+ "\033\\\[31;44;1;27mxxx\033\\\[m" \
+ "apply a style when styling is on"
+}
+
+# Start GDB.
+with_ansi_styling_terminal {
+ clean_restart
+}
+
+# Turn styling off so that the output of 'show style ...' isn't styled, this
+# makes it easier to match the output.
+gdb_test_no_output "set style enabled off"
+
+# Run the tests.
+test_basic_usage
+test_style_prefix
+test_applying
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv5 4/4] gdb/python: extend gdb.write to support styled output
2025-06-25 14:42 ` [PATCHv5 0/4] Python Style API Andrew Burgess
` (2 preceding siblings ...)
2025-06-25 14:42 ` [PATCHv5 3/4] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
@ 2025-06-25 14:42 ` Andrew Burgess
2025-07-07 14:43 ` [PATCHv6 0/4] Python Style API Andrew Burgess
4 siblings, 0 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-06-25 14:42 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
It is already possible to produce styled output from Python by
converting the gdb.Style to its escape code sequence, and writing that
to the output stream.
But this commit adds an alternative option to the mix by extending the
existing gdb.write() function to accept a 'style' argument. The value
of this argument can be 'None' to indicate no style change should be
performed, this is the default, and matches the existing behaviour.
Or the new 'style' argument can be a gdb.Style object, in which case
the specified style is applied only for the string passed to
gdb.write, after which the default style is re-applied.
Using gdb.write with a style object more closely matches how GDB
handles styling internally, and has the benefit that the user doesn't
need to remember to restore the default style when they are done.
---
gdb/NEWS | 3 ++
gdb/doc/python.texi | 9 +++-
gdb/python/py-style.c | 27 ++++++++++
gdb/python/python-internal.h | 15 ++++++
gdb/python/python.c | 30 ++++++++++--
| 49 +++++++++++++++++++
| 16 ++++++
gdb/testsuite/gdb.python/py-style.exp | 29 +++++++++++
8 files changed, 172 insertions(+), 6 deletions(-)
diff --git a/gdb/NEWS b/gdb/NEWS
index 4ae57ca776f..b6126467df0 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -142,6 +142,9 @@ info threads [-gid] [-stopped] [-running] [ID]...
Use gdb.StyleParameterSet(NAME) to create 'set style NAME ...'
and 'show style NAME ...' parameters.
+ ** The gdb.write() function now takes an additional, optional,
+ 'style' argument, which can be used to style the output.
+
** 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 486f0a8f750..55a79b6785d 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -452,7 +452,7 @@ Basic Python
historical compatibility.
@end defun
-@defun gdb.write (string @r{[}, stream@r{]})
+@defun gdb.write (string @r{[}, stream@r{]} @r{[}, style@r{]})
Print a string to @value{GDBN}'s paginated output stream. The
optional @var{stream} determines the stream to print to. The default
stream is @value{GDBN}'s standard output stream. Possible stream
@@ -475,6 +475,13 @@ Basic Python
@value{GDBN}'s log stream (@pxref{Logging Output}).
@end table
+The @var{style} should be a @code{gdb.Style} object (@pxref{Styles In
+Python}), or @code{None} (the default). If @var{style} is @code{None}
+then the current style for @var{stream} will be applied to @var{text}.
+If @var{style} is a @code{gdb.Style} object, then this style is
+applied to @var{text}, after which the default output style is
+restored.
+
Writing to @code{sys.stdout} or @code{sys.stderr} will automatically
call this function and will automatically direct the output to the
relevant stream.
diff --git a/gdb/python/py-style.c b/gdb/python/py-style.c
index b3983af3a85..0e9af1fc4c4 100644
--- a/gdb/python/py-style.c
+++ b/gdb/python/py-style.c
@@ -353,6 +353,15 @@ stylepy_init (PyObject *self, PyObject *args, PyObject *kwargs)
\f
+/* See python-internal.h. */
+
+bool
+gdbpy_is_style (PyObject *obj)
+{
+ gdb_assert (obj != nullptr);
+ return PyObject_TypeCheck (obj, &style_object_type);
+}
+
/* Return the ui_file_style for STYLEPY. If the style cannot be found,
then return an empty optional, and set a Python error. */
@@ -369,6 +378,24 @@ stylepy_to_style (style_object *stylepy)
return style;
}
+/* See python-internal.h. */
+
+std::optional<ui_file_style>
+gdbpy_style_object_to_ui_file_style (PyObject *obj)
+{
+ gdb_assert (obj != nullptr);
+ if (!gdbpy_is_style (obj))
+ {
+ PyErr_Format
+ (PyExc_TypeError, _("argument must be a gdb.Style object, not %s."),
+ Py_TYPE (obj)->tp_name);
+ return {};
+ }
+
+ style_object *style_obj = (style_object *) obj;
+ return stylepy_to_style (style_obj);
+}
+
/* 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
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index 7f4237eecc2..28199691226 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -565,6 +565,21 @@ struct symtab_and_line *sal_object_to_symtab_and_line (PyObject *obj);
frame_info_ptr frame_object_to_frame_info (PyObject *frame_obj);
struct gdbarch *arch_object_to_gdbarch (PyObject *obj);
+/* Return true if OBJ is a gdb.Style object. OBJ must not be NULL. */
+
+extern bool gdbpy_is_style (PyObject *obj);
+
+/* Return the ui_file_style from OBJ, a gdb.Style object. OBJ must not be
+ NULL, but can be a non gdb.Style object, in which case a Python error is
+ set and an empty optional is returned.
+
+ It is also possible that OBJ is a gdb.Style object, but the underlying
+ style cannot be fetched for some reason. If this happens then a Python
+ error is set and an empty optional is returned. */
+
+extern std::optional<ui_file_style>
+ gdbpy_style_object_to_ui_file_style (PyObject *obj);
+
extern PyObject *gdbpy_execute_mi_command (PyObject *self, PyObject *args,
PyObject *kw);
diff --git a/gdb/python/python.c b/gdb/python/python.c
index dddf5c39c58..ad9659d0bbb 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -1561,12 +1561,22 @@ static PyObject *
gdbpy_write (PyObject *self, PyObject *args, PyObject *kw)
{
const char *arg;
- static const char *keywords[] = { "text", "stream", NULL };
+ static const char *keywords[] = { "text", "stream", "style", nullptr };
int stream_type = 0;
+ PyObject *style_obj = Py_None;
- if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s|i", keywords, &arg,
- &stream_type))
- return NULL;
+ if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s|iO", keywords, &arg,
+ &stream_type, &style_obj))
+ return nullptr;
+
+ if (style_obj != Py_None && !gdbpy_is_style (style_obj))
+ {
+ PyErr_Format
+ (PyExc_TypeError,
+ _("'style' argument must be gdb.Style or None, not %s."),
+ Py_TYPE (style_obj)->tp_name);
+ return nullptr;
+ }
try
{
@@ -1584,7 +1594,17 @@ gdbpy_write (PyObject *self, PyObject *args, PyObject *kw)
break;
}
- gdb_puts (arg, stream);
+ if (style_obj == Py_None)
+ gdb_puts (arg, stream);
+ else
+ {
+ std::optional<ui_file_style> style
+ = gdbpy_style_object_to_ui_file_style (style_obj);
+ if (!style.has_value ())
+ return nullptr;
+
+ fputs_styled (arg, style.value (), stream);
+ }
}
catch (const gdb_exception &except)
{
--git a/gdb/testsuite/gdb.python/py-color-pagination.exp b/gdb/testsuite/gdb.python/py-color-pagination.exp
index 7b3dc1859c1..0a2f01e6533 100644
--- a/gdb/testsuite/gdb.python/py-color-pagination.exp
+++ b/gdb/testsuite/gdb.python/py-color-pagination.exp
@@ -110,8 +110,57 @@ proc test_pagination { type mode } {
}
}
+# Run the command 'style-fill-v2' which fills the screen with output and
+# triggers the pagination prompt. Check that styling is applied correctly
+# to the output. This v2 command is exercising passing a style to
+# gdb.write() rather than passing the escape sequence for the style.
+proc test_pagination_v2 { } {
+ set saw_bad_color_handling false
+ set expected_restore_color ""
+ set last_color ""
+ gdb_test_multiple "style-fill-v2" "" {
+ -re "^style-fill-v2\r\n" {
+ exp_continue
+ }
+
+ -re "^((?:${::any_color}\033\\\[m)?)(${::any_color})$::str\033\\\[m" {
+ # After a continuation prompt GDB will restore the previous
+ # color, and then we immediately switch to a new color.
+ set restored_color $expect_out(1,string)
+ if { $restored_color ne ""
+ && $restored_color ne $expected_restore_color } {
+ set saw_bad_color_handling true
+ }
+ set last_color $expect_out(2,string)
+ exp_continue
+ }
+
+ -re "^$::pagination_prompt$" {
+ # After a pagination prompt we expect GDB to restore the last
+ # color, but this will then be disabled due to a styled
+ # gdb.write emitting a return to default style escape sequence.
+ set expected_restore_color "$last_color\033\[m"
+
+ # Send '\n' to view more output.
+ send_gdb "\n"
+ exp_continue
+ }
+
+ -re "^\r\n" {
+ # The matches the newline sent to the continuation prompt.
+ exp_continue
+ }
+
+ -re "^$::gdb_prompt $" {
+ gdb_assert { !$saw_bad_color_handling } $gdb_test_name
+ }
+ }
+}
+
foreach_with_prefix type { color style } {
foreach_with_prefix mode { write print } {
test_pagination $type $mode
}
}
+
+test_pagination_v2
--git a/gdb/testsuite/gdb.python/py-color-pagination.py b/gdb/testsuite/gdb.python/py-color-pagination.py
index f0252e5bf86..9cdc76cb0ce 100644
--- a/gdb/testsuite/gdb.python/py-color-pagination.py
+++ b/gdb/testsuite/gdb.python/py-color-pagination.py
@@ -62,5 +62,21 @@ class StyleTester(gdb.Command):
write(mode, "\n")
+class StyleTester2(gdb.Command):
+ def __init__(self):
+ super().__init__("style-fill-v2", gdb.COMMAND_USER)
+
+ def invoke(self, args, from_tty):
+ str = "<" + "-" * 78 + ">"
+ for i in range(0, 20):
+ for color_name in basic_colors:
+ c = gdb.Color(color_name)
+ s = gdb.Style(foreground=c)
+ gdb.write(str, style=s)
+
+ gdb.write("\n")
+
+
ColorTester()
StyleTester()
+StyleTester2()
diff --git a/gdb/testsuite/gdb.python/py-style.exp b/gdb/testsuite/gdb.python/py-style.exp
index 020d0e0319b..7f3b01333a1 100644
--- a/gdb/testsuite/gdb.python/py-style.exp
+++ b/gdb/testsuite/gdb.python/py-style.exp
@@ -340,3 +340,32 @@ gdb_test_no_output "python input_text = \"a totally different string that is als
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"
+
+# Test gdb.write passing in a style. Define a helper function to
+# ensure all output is flushed before we return to the prompt.
+gdb_test_multiline "create function to call gdb.write then flush" \
+ "python" "" \
+ "def write_and_flush(*args, **kwargs):" "" \
+ " gdb.write(*args, **kwargs)" "" \
+ " gdb.write(\"\\n\")" "" \
+ " gdb.flush(gdb.STDOUT)" "" \
+ "end" ""
+
+gdb_test "python write_and_flush(\"some text\")" \
+ "^some text" "unstyled text, no style passed"
+
+gdb_test "python write_and_flush(\"some text\", style=None)" \
+ "^some text" "unstyled text, pass style as None"
+
+gdb_test "python write_and_flush(\"some text\", style=filename_style)" \
+ "^\033\\\[34;41;2;27msome text\033\\\[m" \
+ "styled output, pass style by keyword"
+
+gdb_test "python write_and_flush(\"some text\", gdb.STDOUT, filename_style)" \
+ "^\033\\\[34;41;2;27msome text\033\\\[m" \
+ "styled output, pass style by position"
+
+gdb_test "python write_and_flush(\"some text\", style='filename')" \
+ [multi_line \
+ "Python Exception <class 'TypeError'>: 'style' argument must be gdb\\.Style or None, not str\\." \
+ "Error occurred in Python: 'style' argument must be gdb\\.Style or None, not str\\."]
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv6 0/4] Python Style API
2025-06-25 14:42 ` [PATCHv5 0/4] Python Style API Andrew Burgess
` (3 preceding siblings ...)
2025-06-25 14:42 ` [PATCHv5 4/4] gdb/python: extend gdb.write to support styled output Andrew Burgess
@ 2025-07-07 14:43 ` Andrew Burgess
2025-07-07 14:43 ` [PATCHv6 1/4] gdb: allow gdb.Color to work correctly with pagination Andrew Burgess
` (4 more replies)
4 siblings, 5 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-07-07 14:43 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
After the gdb.Color support was added to the Python API I wondered if
this would be enough to easily add styling to custom Python commands.
But I don't think it is, there's still lots that would need to be
handled in user code, for example, the intensity setting. And having
styles that track named styles (e.g. if I want to apply the 'filename'
style to some text from Python).
So this series builds on top of the gdb.Color API to provide a new
gdb.Style API which, I hope, makes it easy to start writing Python
commands that can use GDB's styles.
Here's an example of a simple custome command that uses styles:
import os
class user_home_cmd(gdb.Command):
def __init__(self):
super().__init__("user-home", gdb.COMMAND_USER)
self._style = gdb.Style('filename')
def invoke(self, args, from_tty):
print("The filename is %s."
% (self._style.apply(os.environ['HOME'])))
user_home_cmd()
The new 'user-home' command prints the current users home directory
using 'filename' style. Changing the 'filename' style and re-running
the command will track the style changes.
In v2:
- Fixed an issue with the py-style-parameter-set.exp test; GDB was
not started in a terminal with styling support in one case, so
tests would fail depending on how the testsuite was being run.
In v3:
- Another test issue. This time, the error message given when
parsing an argument of the wrong type changes with Python version.
Relax the regexp to accept any 'TypeError' message.
In v4:
- Two new patches. First patch extends pager_file so that
gdb.Color, and gdb.Style when it's added can work well with GDB's
pager. Last patch extends the gdb.write() Python function to also
support styling.
- I've renamed some of the classes in python/py-style.c, nothing
major, I replaced 'stylepy' with 'style' in some names. I think
the new names are clearer, and better match the rest of GDB's
Python API code.
- Unfortunately, the new pager_file changes mean that this patch
series now depends on this other patch:
https://inbox.sourceware.org/gdb-patches/444008aeae2bb3c68cf868fa317374b3d7973860.1750197766.git.aburgess@redhat.com
Without that patch there will be some failures in
gdb.python/py-color-pagination.exp. I'm not proposing to merge
this until that other patch, or something like it, is merged. But
this series could be reviewed independently.
In v5:
- The pager_file styling fixes mentioned for v4 have now been merged
to master. I've rebased this series on top of them, and all the
tests are now passing.
- I've tweaked the wording for a couple of the commit messages just
to make things clearer.
- No real code changes.
In v6:
- Rebased as there were some style related changes merged to master
recently. No conflicts, and everything still works fine, so there
are no real changes here.
Thanks,
Andrew
---
Andrew Burgess (4):
gdb: allow gdb.Color to work correctly with pagination
gdb/python: add gdb.Style class
gdb/python: new class gdb.StyleParameterSet
gdb/python: extend gdb.write to support styled output
gdb/Makefile.in | 1 +
gdb/NEWS | 14 +
gdb/doc/python.texi | 262 +++++-
gdb/python/lib/gdb/__init__.py | 209 +++++
gdb/python/py-style.c | 824 ++++++++++++++++++
gdb/python/python-internal.h | 15 +
gdb/python/python.c | 46 +-
.../gdb.python/py-color-pagination.exp | 166 ++++
.../gdb.python/py-color-pagination.py | 82 ++
.../gdb.python/py-style-parameter-set.exp | 366 ++++++++
gdb/testsuite/gdb.python/py-style.exp | 371 ++++++++
gdb/ui-style.h | 6 +
gdb/utils.c | 21 +-
13 files changed, 2366 insertions(+), 17 deletions(-)
create mode 100644 gdb/python/py-style.c
create mode 100644 gdb/testsuite/gdb.python/py-color-pagination.exp
create mode 100644 gdb/testsuite/gdb.python/py-color-pagination.py
create mode 100644 gdb/testsuite/gdb.python/py-style-parameter-set.exp
create mode 100644 gdb/testsuite/gdb.python/py-style.exp
base-commit: 1fe6ffd81e76a2799add18d9e1d5208a90c21ece
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv6 1/4] gdb: allow gdb.Color to work correctly with pagination
2025-07-07 14:43 ` [PATCHv6 0/4] Python Style API Andrew Burgess
@ 2025-07-07 14:43 ` Andrew Burgess
2025-07-07 14:43 ` [PATCHv6 2/4] gdb/python: add gdb.Style class Andrew Burgess
` (3 subsequent siblings)
4 siblings, 0 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-07-07 14:43 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
This commit allows gdb.Color objects to be used to style output from
GDB commands written in Python, and the styled output should work
correctly with pagination.
There are two parts to fixing this:
First, GDB needs to be able to track the currently applied style
within the page_file class. This means that style changes need to be
achieved with calls to pager_file::emit_style_escape.
Now usually, GDB does this by calling something like fprintf_styled,
which takes care to apply the style for us. However, that's not
really an option here as a gdb.Color isn't a full style, and as the
gdb.Color object is designed to be converted directly into escape
sequences that can then be printed, we really need a solution that
works with this approach.
However pager_file::puts already has code in place to handle escape
sequences. Right now all this code does is spot the escape sequence
and append it to the m_wrap_buffer. But in this commit I propose that
we go one step further, parse the escape sequence back into a
ui_file_style object in pager_file::puts, and then we can call
pager_file::emit_style_escape.
If the parsing doesn't work then we can just add the escape sequence
to m_wrap_buffer as we did before.
But wait, how can this work if a gdb.Color isn't a full style? Turns
out that's not a problem. We only ever emit the escape sequence for
those parts of a style that need changing, so a full style that sets
the foreground color will emit the same escape sequence as a gdb.Color
for the foreground. When we convert the escape sequence back into a
ui_file_style, then we get a style with everything set to default,
except the foreground color.
I had hoped that this would be all that was needed. But unfortunately
this doesn't work because of the second problem...
... the implementation of the Python function gdb.write() calls
gdb_printf(), which calls gdb_vprintf(), which calls ui_file::vprintf,
which calls ui_out::vmessage, which calls ui_out::call_do_message, and
finally we reach cli_ui_out::do_message. This final do_message
function does this:
ui_file *stream = m_streams.back ();
stream->emit_style_escape (style);
stream->puts (str.c_str ());
stream->emit_style_escape (ui_file_style ());
If we imagine the case where we are emitting a style, triggered from
Python like this:
gdb.write(gdb.Color('red').escape_sequence(True))
the STYLE in this case will be the default ui_file_style(), and STR
will hold the escape sequence we are writing.
After the first change, where pager_file::puts now calls
pager_file::emit_style_escape, the current style of STREAM will have
been updated. But this means that the final emit_style_escape will
now restore the default style.
The fix for this is to avoid using the high level gdb_printf from
gdb.write(), and instead use gdb_puts instead. The gdb_puts function
doesn't restore the default style, which means our style modification
survives.
There's a new test included. This test includes what appears like a
pointless extra loop (looping over a single value), but later commits
in this series will add more values to this list.
---
gdb/python/python.c | 18 +--
| 116 ++++++++++++++++++
| 46 +++++++
gdb/utils.c | 21 +++-
4 files changed, 189 insertions(+), 12 deletions(-)
create mode 100644 gdb/testsuite/gdb.python/py-color-pagination.exp
create mode 100644 gdb/testsuite/gdb.python/py-color-pagination.py
diff --git a/gdb/python/python.c b/gdb/python/python.c
index cb0d642a67d..1af7896eb08 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -1570,21 +1570,21 @@ gdbpy_write (PyObject *self, PyObject *args, PyObject *kw)
try
{
+ ui_file *stream;
switch (stream_type)
{
case 1:
- {
- gdb_printf (gdb_stderr, "%s", arg);
- break;
- }
+ stream = gdb_stderr;
+ break;
case 2:
- {
- gdb_printf (gdb_stdlog, "%s", arg);
- break;
- }
+ stream = gdb_stdlog;
+ break;
default:
- gdb_printf (gdb_stdout, "%s", arg);
+ stream = gdb_stdout;
+ break;
}
+
+ gdb_puts (arg, stream);
}
catch (const gdb_exception &except)
{
--git a/gdb/testsuite/gdb.python/py-color-pagination.exp b/gdb/testsuite/gdb.python/py-color-pagination.exp
new file mode 100644
index 00000000000..cb410da2274
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-color-pagination.exp
@@ -0,0 +1,116 @@
+# 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.Color and how this
+# interacts with GDB's pagination system.
+
+load_lib gdb-python.exp
+
+require allow_python_tests
+
+standard_testfile
+
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+
+set str "<[string repeat - 78]>"
+
+set black "(?:\033\\\[30;49;22;27m)"
+set red "(?:\033\\\[31;49;22;27m)"
+set green "(?:\033\\\[32;49;22;27m)"
+set yellow "(?:\033\\\[33;49;22;27m)"
+set blue "(?:\033\\\[34;49;22;27m)"
+set magenta "(?:\033\\\[35;49;22;27m)"
+set cyan "(?:\033\\\[36;49;22;27m)"
+set white "(?:\033\\\[37;49;22;27m)"
+
+set any_color "(?:${black}|${red}|${green}|${yellow}|${blue}|${magenta}|${cyan}|${white})"
+
+# Run the command 'TYPE-fill MODE' which fills the screen with output and
+# triggers the pagination prompt. Check that styling is applied correctly
+# to the output.
+proc test_pagination { type mode } {
+
+ # Start with a fresh GDB, but enable color support.
+ with_ansi_styling_terminal {
+ clean_restart
+ }
+
+ gdb_test_no_output "source $::pyfile" "source the script"
+
+ gdb_test_no_output "set width 80"
+ gdb_test_no_output "set height 15"
+
+ set saw_bad_color_handling false
+ set expected_restore_color ""
+ set last_color ""
+ gdb_test_multiple "$type-fill $mode" "" {
+ -re "^$type-fill $mode\r\n" {
+ exp_continue
+ }
+
+ -re "^(${::any_color}?)(${::any_color})$::str" {
+ # After a continuation prompt GDB will restore the previous
+ # color, and then we immediately switch to a new color.
+ set restored_color $expect_out(1,string)
+ if { $restored_color ne ""
+ && $restored_color ne $expected_restore_color } {
+ set saw_bad_color_handling true
+ }
+ set last_color $expect_out(2,string)
+ exp_continue
+ }
+
+ -re "^\033\\\[${::decimal}m$::str" {
+ # This catches the case where the color's escape sequence has
+ # not been converted back into a full style. This indicates
+ # something went wrong in the pager_file::puts function.
+ set saw_bad_color_handling true
+ exp_continue
+ }
+
+ -re "^((?:\033\\\[m)?)$::pagination_prompt$" {
+ # After a pagination prompt we expect GDB to restore the last
+ # color.
+ set expected_restore_color $last_color
+
+ # If we didn't see a color reset sequence then the pagination
+ # prompt will have been printed in the wrong color, this is a
+ # GDB bug.
+ set color_reset $expect_out(1,string)
+ if { $color_reset eq "" } {
+ set saw_bad_color_handling true
+ }
+
+ # Send '\n' to view more output.
+ send_gdb "\n"
+ exp_continue
+ }
+
+ -re "^\r\n" {
+ # The matches the newline sent to the continuation prompt.
+ exp_continue
+ }
+
+ -re "^\033\\\[m\r\n$::gdb_prompt $" {
+ gdb_assert { !$saw_bad_color_handling } $gdb_test_name
+ }
+ }
+}
+
+foreach_with_prefix type { color } {
+ foreach_with_prefix mode { write print } {
+ test_pagination $type $mode
+ }
+}
--git a/gdb/testsuite/gdb.python/py-color-pagination.py b/gdb/testsuite/gdb.python/py-color-pagination.py
new file mode 100644
index 00000000000..efd501eedf5
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-color-pagination.py
@@ -0,0 +1,46 @@
+# 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/>.
+
+import gdb
+
+basic_colors = ["black", "red", "green", "yellow", "blue", "magenta", "cyan", "white"]
+
+
+def write(mode, text):
+ if mode == "write":
+ gdb.write(text)
+ else:
+ print(text, end="")
+
+
+class ColorTester(gdb.Command):
+ def __init__(self):
+ super().__init__("color-fill", gdb.COMMAND_USER)
+
+ def invoke(self, args, from_tty):
+ mode = args
+ str = "<" + "-" * 78 + ">"
+ for i in range(0, 20):
+ for color_name in basic_colors:
+ c = gdb.Color(color_name)
+ write(mode, c.escape_sequence(True))
+ write(mode, str)
+
+ default = gdb.Color("none")
+ write(mode, default.escape_sequence(True))
+ write(mode, "\n")
+
+
+ColorTester()
diff --git a/gdb/utils.c b/gdb/utils.c
index 10d3d51e481..92e626a9c75 100644
--- a/gdb/utils.c
+++ b/gdb/utils.c
@@ -1702,10 +1702,25 @@ pager_file::puts (const char *linebuffer)
else if (*linebuffer == '\033'
&& skip_ansi_escape (linebuffer, &skip_bytes))
{
- m_wrap_buffer.append (linebuffer, skip_bytes);
- /* Note that we don't consider this a character, so we
+ /* We don't consider escape sequences as characters, so we
don't increment chars_printed here. */
- linebuffer += skip_bytes;
+
+ size_t style_len;
+ ui_file_style style;
+ if (style.parse (linebuffer, &style_len)
+ && style_len <= skip_bytes)
+ {
+ this->emit_style_escape (style);
+
+ linebuffer += style_len;
+ skip_bytes -= style_len;
+ }
+
+ if (skip_bytes > 0)
+ {
+ m_wrap_buffer.append (linebuffer, skip_bytes);
+ linebuffer += skip_bytes;
+ }
}
else if (*linebuffer == '\r')
{
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv6 2/4] gdb/python: add gdb.Style class
2025-07-07 14:43 ` [PATCHv6 0/4] Python Style API Andrew Burgess
2025-07-07 14:43 ` [PATCHv6 1/4] gdb: allow gdb.Color to work correctly with pagination Andrew Burgess
@ 2025-07-07 14:43 ` Andrew Burgess
2025-07-07 14:43 ` [PATCHv6 3/4] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
` (2 subsequent siblings)
4 siblings, 0 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-07-07 14:43 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess, Eli Zaretskii
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 ++++++++++++++++++
| 5 +-
| 20 +
gdb/testsuite/gdb.python/py-style.exp | 342 ++++++++
gdb/ui-style.h | 6 +
8 files changed, 1316 insertions(+), 2 deletions(-)
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 fc0c56564c2..d6950d45453 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 4c9aed421bb..303374377ba 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -137,6 +137,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 6fa22851f6a..0b4ed9d6a1c 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.
@@ -7262,6 +7263,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..b3983af3a85
--- /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 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;
+
+ 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)
+{
+ 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;
+ (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)
+{
+ 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);
--git a/gdb/testsuite/gdb.python/py-color-pagination.exp b/gdb/testsuite/gdb.python/py-color-pagination.exp
index cb410da2274..7b3dc1859c1 100644
--- a/gdb/testsuite/gdb.python/py-color-pagination.exp
+++ b/gdb/testsuite/gdb.python/py-color-pagination.exp
@@ -14,7 +14,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# This file is part of the GDB testsuite. It tests gdb.Color and how this
-# interacts with GDB's pagination system.
+# interacts with GDB's pagination system. The test also tests gdb.Style
+# because the tests are very similar.
load_lib gdb-python.exp
@@ -109,7 +110,7 @@ proc test_pagination { type mode } {
}
}
-foreach_with_prefix type { color } {
+foreach_with_prefix type { color style } {
foreach_with_prefix mode { write print } {
test_pagination $type $mode
}
--git a/gdb/testsuite/gdb.python/py-color-pagination.py b/gdb/testsuite/gdb.python/py-color-pagination.py
index efd501eedf5..f0252e5bf86 100644
--- a/gdb/testsuite/gdb.python/py-color-pagination.py
+++ b/gdb/testsuite/gdb.python/py-color-pagination.py
@@ -43,4 +43,24 @@ class ColorTester(gdb.Command):
write(mode, "\n")
+class StyleTester(gdb.Command):
+ def __init__(self):
+ super().__init__("style-fill", gdb.COMMAND_USER)
+
+ def invoke(self, args, from_tty):
+ mode = args
+ str = "<" + "-" * 78 + ">"
+ for i in range(0, 20):
+ for color_name in basic_colors:
+ c = gdb.Color(color_name)
+ s = gdb.Style(foreground=c)
+ write(mode, s.escape_sequence())
+ write(mode, str)
+
+ default = gdb.Style()
+ write(mode, default.escape_sequence())
+ write(mode, "\n")
+
+
ColorTester()
+StyleTester()
diff --git a/gdb/testsuite/gdb.python/py-style.exp b/gdb/testsuite/gdb.python/py-style.exp
new file mode 100644
index 00000000000..020d0e0319b
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-style.exp
@@ -0,0 +1,342 @@
+# 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"
+
+# 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"
diff --git a/gdb/ui-style.h b/gdb/ui-style.h
index f61152f7517..ded63878ad7 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
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv6 3/4] gdb/python: new class gdb.StyleParameterSet
2025-07-07 14:43 ` [PATCHv6 0/4] Python Style API Andrew Burgess
2025-07-07 14:43 ` [PATCHv6 1/4] gdb: allow gdb.Color to work correctly with pagination Andrew Burgess
2025-07-07 14:43 ` [PATCHv6 2/4] gdb/python: add gdb.Style class Andrew Burgess
@ 2025-07-07 14:43 ` Andrew Burgess
2025-07-07 14:43 ` [PATCHv6 4/4] gdb/python: extend gdb.write to support styled output Andrew Burgess
2025-08-13 10:25 ` [PATCHv7 0/4] Fix for gdb.Color + pagination AND new gdb.Style API Andrew Burgess
4 siblings, 0 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-07-07 14:43 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
Add a new helper class gdb.StyleParameterSet. This new class can be
used to simplify creation of new style parameter sets. A style
parameter set is the 'foreground', 'background', and (optionally), the
'intensity' settings, all grouped under a single prefix command.
And example usage is:
(gdb) python s = gdb.StyleParameterSet("my-style")
(gdb) show style my-style
style my-style background: The "my-style" style background color is: none
style my-style foreground: The "my-style" style foreground color is: none
style my-style intensity: The "my-style" style display intensity is: normal
(gdb)
Having created a gdb.StyleParameterSet, the object itself can be used
to access a named style corresponding to the setting group, like this:
(gdb) python print(s.style)
<gdb.Style name='my-style', fg=none, bg=none, intensity=normal>
(gdb)
Of course, having access to the gdb.Style makes it easy to change the
settings, or the settings can be adjusted via the normal CLI 'set'
commands.
As gdb.StyleParameterSet manages a set of parameters, and the
gdb.Parameter class uses Parameter.value as the attribute to read the
parameter's value, there is also StyleParameterSet.value, but this is
just an alias for StyleParameterSet.style, that is, it allows the
gdb.Style object to be read and written too.
It is worth noting that this class only creates a single level of
prefix command. As an example GDB has style 'disassembler mnemonic',
where the 'disassembler' part is a group of related styles. If a user
wanted to create:
style
my-style-group
style-1
style-2
style-3
Where each of 'style-1', 'style-2', and 'style-3' will have the full
set of 'foreground', 'background', and 'intensity', then the
gdb.StyleParameterSet can be used to create the 'style-N' part, but
the user will have to create the 'my-style-group' prefix themselves,
possibly using gdb.ParameterPrefix, e.g.:
gdb.ParameterPrefix("style my-style-group", gdb.COMMAND_NONE)
gdb.StyleParameterSet("my-style-group style-1")
gdb.StyleParameterSet("my-style-group style-2")
gdb.StyleParameterSet("my-style-group style-3")
---
gdb/NEWS | 4 +
gdb/doc/python.texi | 115 +++++-
gdb/python/lib/gdb/__init__.py | 209 ++++++++++
.../gdb.python/py-style-parameter-set.exp | 366 ++++++++++++++++++
4 files changed, 693 insertions(+), 1 deletion(-)
create mode 100644 gdb/testsuite/gdb.python/py-style-parameter-set.exp
diff --git a/gdb/NEWS b/gdb/NEWS
index 303374377ba..6ae6710e877 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -144,6 +144,10 @@ info threads [-gid] [-stopped] [-running] [ID]...
gdb.INTENSITY_DIM for use with gdb.Style when representing
intensities.
+ ** New gdb.StyleParameterSet for creating custom style settings.
+ Use gdb.StyleParameterSet(NAME) to create 'set style NAME ...'
+ and 'show style NAME ...' parameters.
+
** 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 0b4ed9d6a1c..486f0a8f750 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -5293,6 +5293,7 @@ Parameters In Python
feature-2}, then the @kbd{plugin-name} would need to be a prefix
command (@pxref{CLI Commands In Python}).
+@anchor{gdb.ParameterPrefix}
However, when creating parameters, you will almost always need to
create two prefix commands, one as a @kbd{set} sub-command, and one as
a @kbd{show} sub-command. @value{GDBN} provides the
@@ -5379,6 +5380,117 @@ Parameters In Python
ExampleParam("plugin-name feature-2")
@end smallexample
+@anchor{Creating Style Parameters}
+The helper class @code{gdb.StyleParameterSet} exists to make it easier
+to create new styles. @value{GDBN} places style settings under
+@samp{show style @dots{}} and @samp{set style @dots{}}, an example of a style
+is @samp{filename}. Each style is really a prefix command (@pxref{CLI
+Commands In Python}), with sub-commands @samp{foreground},
+@samp{background}, and optionally, @samp{intensity}.
+
+It is simple enough to create a new style using two @code{gdb.Command}
+objects for the prefix commands (one for @samp{set}, and one for
+@samp{show}), and three @code{gdb.Parameter} objects, one each for the
+@samp{foreground}, @samp{background}, and @samp{intensity}. You would
+also want to take care to craft the help text so that the new style
+behaves the same as the existing styles.
+
+Or, you can use the @code{gdb.StyleParameterSet} class, which takes
+care of all this, as the following example shows:
+
+@smallexample
+@group
+(@value{GDBP}) python s = gdb.StyleParameterSet("my-style")
+(@value{GDBP}) show style my-style
+style my-style background: The "my-style" style background color is: none
+style my-style foreground: The "my-style" style foreground color is: none
+style my-style intensity: The "my-style" style display intensity is: normal
+(@value{GDBP})
+@end group
+@end smallexample
+
+You might also want to group a number of styles within a new prefix,
+similar to how @value{GDBN} groups disassembler related styles within
+the @samp{style disassembler} prefix. This can be done using
+@code{gdb.ParameterPrefix} (@pxref{gdb.ParameterPrefix}), as in this
+example:
+
+@smallexample
+@group
+(@value{GDBP}) python gdb.ParameterPrefix("style my-group", gdb.COMMAND_NONE)
+(@value{GDBP}) python s_a = gdb.StyleParameterSet("my-group aaa")
+(@value{GDBP}) python s_b = gdb.StyleParameterSet("my-group bbb")
+(@value{GDBP}) show style my-group
+style my-group aaa background: The "my-group aaa" style background color is: none
+style my-group aaa foreground: The "my-group aaa" style foreground color is: none
+style my-group aaa intensity: The "my-group aaa" style display intensity is: normal
+style my-group bbb background: The "my-group bbb" style background color is: none
+style my-group bbb foreground: The "my-group bbb" style foreground color is: none
+style my-group bbb intensity: The "my-group bbb" style display intensity is: normal
+(@value{GDBP})
+@end group
+@end smallexample
+
+The @code{gdb.StyleParameterSet} class has the following methods and
+attributes:
+
+@defun StyleParameterSet.__init__(name, @w{add_intensity=@code{True}}, @w{doc=@code{None}})
+Create a new style group based on @var{name}, which is a string. For
+example if @var{name} is @samp{my-style}, then @value{GDBN} will
+create the prefix commands @samp{set style my-style} and @samp{show
+style my-style}. Within these prefix commands will be
+@samp{foreground}, @samp{background}, and @samp{intensity} parameters
+with the appropriate types.
+
+It is also possible for @var{name} to consist of multiple words, so
+long as each prefix command (except the last one) already exists. For
+example, it is valid to use a @var{name} value of @samp{disassembler
+my-style}, as the @samp{disassembler} prefix command already exists.
+@value{GDBN} would then create @samp{set style disassembler my-style}
+and @samp{show style disassembler my-style}, and within the
+@samp{my-style} prefixes will be the @samp{foreground},
+@samp{background}, and @samp{intensity} parameters with the
+appropriate types.
+
+Every style requires a @samp{foreground} and @samp{background}, but
+not every style needs an @samp{intensity}. If @var{add_intensity} is
+@code{True} (the default), then the @samp{intensity} parameter will be
+created. If @var{add_intensity} is @code{False}, then the
+@samp{intensity} parameter will not be created.
+
+If the @samp{intensity} parameter is not created, then the
+@code{gdb.Style} (@pxref{Styles In Python}) created from this
+@code{gdb.StyleParameterSet} will have @code{gdb.INTENSITY_NORMAL}.
+
+The @var{doc} should be a string which will be used as the help text
+for the @var{name} prefix command. This text is used as the
+@code{Command.__doc__} value for the @code{gdb.Command} object that is
+the prefix command object (@pxref{CLI Commands In Python}). If
+@var{doc} is @code{None} (the default) then a basic default help text
+is used.
+@end defun
+
+@defun StyleParameterSet.apply(string)
+Equivalent to @code{StyleParameterSet.style.apply(string)}. Returns a
+copy of @var{string} with escape sequences added to the start and end.
+The escape sequence at the start applies this style, and the escape
+sequence at the end restores the terminal default.
+
+If styling is disabled (i.e.@: @samp{set style enabled off}), then no
+escape sequences are added and this method returns a copy of
+@var{string}.
+@end defun
+
+@defvar StyleParameterSet.style
+This read/write attribute holds a @code{gdb.Style} object
+(@pxref{Styles In Python}), that is a named style associated with this
+style parameter group.
+@end defvar
+
+@defvar StyleParameterSet.value
+This is an alias for @code{StyleParameterSet.style}, see above.
+@end defvar
+
@node Functions In Python
@subsubsection Writing new convenience functions
@@ -7275,7 +7387,8 @@ Styles In Python
@value{GDBN} has many styles builtin (@pxref{Output Styling}), and
style objects can be created that apply these builtin styles to Python
-output.
+output. It is also possible to create new styles which can be used to
+style Python output (@pxref{Creating Style Parameters}).
The style class is called @code{gdb.Style}, and has the following
methods and attributes:
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index cedd897ab0f..f2f57280007 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -515,3 +515,212 @@ class ParameterPrefix:
def dont_repeat(self):
if self.active_prefix is not None:
self.active_prefix.dont_repeat()
+
+
+class StyleParameterSet:
+ """Create new style parameters.
+
+ A style parameter is a set of parameters that start with 'set style ...'
+ and 'show style ...'. For example 'filename' is a style parameter, and
+ 'disassembler symbol' is another style parameter.
+
+ The name of the style parameter is really a prefix command. Under this
+ we must have two commands 'foreground' and 'background', which are color
+ parameters. A third, optional command 'intensity', is an enum with
+ values 'normal', 'bold', and 'dim'.
+
+ A StyleParameterSet is initialised with a name, e.g. 'filename' or
+ 'disassembler symbol'. The StyleParameterSet creates the prefix
+ commands in the 'set style' and 'show style' name space, and then adds
+ the 'foreground', 'background', and optionally, the 'intensity'
+ commands.
+
+ If you want a whole new style group, similar to 'disassembler',
+ then you need to add this yourself first, then StyleParameterSet
+ can be used to create styles within the new prefix group.
+
+ The 'value' attribute on this object can be used to get and set a
+ gdb.Style object which controls all aspects of this style.
+
+ For readability, the alias 'style' is the same as 'value'.
+ """
+
+ def __init__(self, name, add_intensity=True, doc=None):
+ # The STYLE_NAME is something like 'filename' is 'set style
+ # filename ...', and PARAM_NAME is one of 'foreground',
+ # 'background', or 'intensity'. The DESC_TEXT is the long
+ # form used in help text, like 'foreground color' or 'display
+ # intensity'. The DEFAULT_VALUE is used to set the SELF.value
+ # attribute. And PARAM_TYPE is a gdb.PARAM_* constant. The
+ # ARGS is used for gdb.PARAM_ENUM, which ARGS should be the
+ # enum value list.
+ class style_parameter(Parameter):
+ def __init__(
+ self,
+ style_name,
+ parent_obj,
+ param_name,
+ desc_text,
+ default_value,
+ param_type,
+ *args
+ ):
+ # Setup documentation must be done before calling
+ # parent's __init__ method, as the __init__ reads (and
+ # copies) these values.
+ self.show_doc = "Show the " + desc_text + " for this property."
+ self.set_doc = "Set the " + desc_text + " for this property."
+ self.__doc__ = ""
+
+ # Call the parent's __init__ method to actually create
+ # the parameter.
+ super().__init__(
+ "style " + style_name + " " + param_name,
+ COMMAND_NONE,
+ param_type,
+ *args
+ )
+
+ # Store information we need in other methods.
+ self._style_name = style_name
+ self._desc_text = desc_text
+ self._parent = parent_obj
+
+ # Finally, setup the default value.
+ self.value = default_value
+
+ # Return the 'show style <style-name> <attribute>' string,
+ # which has styling applied.
+ def get_show_string(self, value):
+ s = self._parent.style
+ return (
+ "The "
+ + s.apply('"' + self._style_name + '" style')
+ + " "
+ + self._desc_text
+ + " is: "
+ + value
+ )
+
+ class style_foreground_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "foreground",
+ "foreground color",
+ Color(),
+ PARAM_COLOR,
+ )
+
+ class style_background_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "background",
+ "background color",
+ Color(),
+ PARAM_COLOR,
+ )
+
+ class style_intensity_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "intensity",
+ "display intensity",
+ "normal",
+ PARAM_ENUM,
+ ["normal", "bold", "dim"],
+ )
+
+ if doc is None:
+ doc = (
+ "The "
+ + name
+ + " display styling.\nConfigure "
+ + name
+ + " colors and display intensity."
+ )
+
+ ParameterPrefix("style " + name, COMMAND_NONE, doc)
+ self._foreground = style_foreground_parameter(name, self)
+ self._background = style_background_parameter(name, self)
+ if add_intensity:
+ self._intensity = style_intensity_parameter(name, self)
+ self._name = name
+ self._style = None
+
+ @property
+ def value(self):
+ """Return the gdb.Style object for this parameter set."""
+ if self._style is None:
+ self._style = Style(self._name)
+ return self._style
+
+ @property
+ def style(self):
+ """Return the gdb.Style object for this parameter set.
+
+ This is an alias for self.value."""
+ return self.value
+
+ @value.setter
+ def value(self, new_value):
+ """Set this parameter set to NEW_VALUE, a gdb.Style object.
+
+ The attributes of NEW_VALUE are used to update the current settings
+ of this parameter set. If this parameter set was created without
+ an intensity setting, then the intensity of NEW_VALUE is ignored."""
+ if not isinstance(new_value, Style):
+ raise TypeError("value must be gdb.Style, not %s" % type(new_value))
+ self._foreground.value = new_value.foreground
+ self._background.value = new_value.background
+ if hasattr(self, "_intensity"):
+ intensity_value = new_value.intensity
+ if intensity_value == INTENSITY_BOLD:
+ intensity_string = "bold"
+ elif intensity_value == INTENSITY_DIM:
+ intensity_string = "dim"
+ elif intensity_value == INTENSITY_NORMAL:
+ intensity_string = "normal"
+ else:
+ raise ValueError(
+ "unknown intensity value %d from Style" % intensity_value
+ )
+
+ self._intensity.value = intensity_string
+
+ @style.setter
+ def style(self, new_value):
+ """Set this parameter set to NEW_VALUE, a gdb.Style object.
+
+ This is an alias for self.value."""
+ self.value = new_value
+
+ def apply(self, *args, **kwargs):
+ """Apply this style to the arguments.
+
+ Forwards all arguments to self.style.apply(). The arguments should be a string,
+ to which this style is applied. This function returns the same string with
+ escape sequences added to apply this style.
+
+ If styling is globally disabled ('set style enabled off') then no escape sequences
+ will be addedded, the input string is returned."""
+ return self.style.apply(*args, **kwargs)
+
+ def __repr__(self):
+ """A string representation of SELF."""
+
+ def full_typename(obj):
+ module = type(obj).__module__
+ qualname = type(obj).__qualname__
+
+ if module is None or module == "builtins":
+ return qualname
+ else:
+ return module + "." + qualname
+
+ return "<" + full_typename(self) + " name='" + self._name + "'>"
diff --git a/gdb/testsuite/gdb.python/py-style-parameter-set.exp b/gdb/testsuite/gdb.python/py-style-parameter-set.exp
new file mode 100644
index 00000000000..9e05acb84cc
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-style-parameter-set.exp
@@ -0,0 +1,366 @@
+# 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.StyleParameterSet.
+
+load_lib gdb-python.exp
+
+require allow_python_tests
+
+# Create a regexp that can be used to match the output of a 'show style
+# NAME' command. HAS_INTENSITY is a boolean and indicates if style NAME has
+# an intensity attribute.
+proc gen_show_style_re { name has_intensity } {
+ set output \
+ [list \
+ "style ${name} background: The \"${name}\" style background color is: none" \
+ "style ${name} foreground: The \"${name}\" style foreground color is: none"]
+
+ if { $has_intensity } {
+ lappend output \
+ "style ${name} intensity: The \"${name}\" style display intensity is: normal"
+ }
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'set style NAME' command,
+# that is, a 'set' command that doesn't actually set an attribute of a
+# style, in this case GDB will print some useful help text. HAS_INTENSITY is
+# a boolean and indicates if style NAME has an intensity attribute or not.
+proc gen_set_style_re { name has_intensity } {
+ set output \
+ [list \
+ "List of \"set style $name\" subcommands:" \
+ "" \
+ "set style $name background -- Set the background color for this property\\." \
+ "set style $name foreground -- Set the foreground color for this property\\."]
+
+ if { $has_intensity } {
+ lappend output \
+ "set style $name intensity -- Set the display intensity for this property\\."
+ }
+
+ lappend output \
+ "" \
+ "Type \"help set style $name\" followed by subcommand name for full documentation\\." \
+ "Type \"apropos word\" to search for commands related to \"word\"\\." \
+ "Type \"apropos -v word\" for full documentation of commands related to \"word\"\\." \
+ "Command name abbreviations are allowed if unambiguous\\."
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'help show style NAME'
+# command. HAS_INTENSITY is a boolean and indicates if style NAME has an
+# intensity attribute or not. DOC is a regexp that matches the doc string
+# for style NAME.
+proc gen_help_show_style_re { name has_intensity doc } {
+ set output \
+ [list \
+ $doc \
+ "" \
+ "List of \"show style ${name}\" subcommands:" \
+ "" \
+ "show style $name background -- Show the background color for this property\\." \
+ "show style $name foreground -- Show the foreground color for this property\\."]
+ if { $has_intensity } {
+ lappend output \
+ "show style $name intensity -- Show the display intensity for this property\\."
+ }
+
+ lappend output \
+ "" \
+ "Type \"help show style $name\" followed by subcommand name for full documentation\\." \
+ "Type \"apropos word\" to search for commands related to \"word\"\\." \
+ "Type \"apropos -v word\" for full documentation of commands related to \"word\"\\." \
+ "Command name abbreviations are allowed if unambiguous\\."
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'help set style NAME'
+# command. HAS_INTENSITY is a boolean and indicates if style NAME has an
+# intensity attribute or not. DOC is a regexp that matches the doc string
+# for style NAME.
+proc gen_help_set_style_re { name has_intensity doc } {
+ return \
+ [multi_line \
+ $doc \
+ "" \
+ [gen_set_style_re $name $has_intensity]]
+}
+
+# Create styles with and without intensity. Use named and unnamed
+# argument passing, and vary the argument passing order. Create
+# styles with and without documentation.
+#
+# Confirm that the styles contain the expected sub-commands, and that
+# the documentation is as expected.
+proc_with_prefix test_basic_usage {} {
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"style-1\")" \
+ "create style-1"
+
+ gdb_test_no_output \
+ "python style_2 = gdb.StyleParameterSet(add_intensity=True, name=\"style-2\")" \
+ "create style-2"
+
+ gdb_test_no_output \
+ "python style_3 = gdb.StyleParameterSet(name=\"style-3\", doc=\"Style-3 display styling.\\nThis is a multi-line documentation\\nstring describing style-3.\")" \
+ "create style-3"
+
+ gdb_test_no_output \
+ "python style_4 = gdb.StyleParameterSet(\"style-4\", add_intensity=False)" \
+ "create style-4"
+
+ foreach style { style-1 style-2 style-3 } {
+ gdb_test "show style $style" \
+ [gen_show_style_re $style true]
+ gdb_test "set style $style" \
+ [gen_set_style_re $style true]
+ }
+
+ gdb_test "show style style-4" \
+ [gen_show_style_re "style-4" false]
+
+ foreach style { style-1 style-2 } {
+ gdb_test "help show style $style" \
+ [gen_help_show_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+
+
+ set out [gen_help_set_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+
+ gdb_test "help set style $style" \
+ [gen_help_set_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+ }
+
+ gdb_test "help show style style-3" \
+ [gen_help_show_style_re "style-3" true \
+ [multi_line \
+ "Style-3 display styling\\." \
+ "This is a multi-line documentation" \
+ "string describing style-3\\."]]
+
+ gdb_test "help show style style-4" \
+ [gen_help_show_style_re "style-4" false \
+ [multi_line \
+ "The style-4 display styling\\." \
+ "Configure style-4 colors and display intensity\\."]]
+
+ for { set i 1 } { $i < 5 } { incr i } {
+ gdb_test "python print(style_$i)" \
+ "<gdb.StyleParameterSet name='style-$i'>" \
+ "print repr of style_$i"
+ }
+
+ # There is no 'style-4 intensity' property.
+ gdb_test "show style style-4 foreground" \
+ "The \"style-4\" style foreground color is: none"
+ gdb_test "show style style-4 background" \
+ "The \"style-4\" style background color is: none"
+ gdb_test "show style style-4 intensity" \
+ "Undefined show style style-4 command: \"intensity\"\\. Try \"help show style style-4\"\\."
+
+ # There is a 'style-1 intensity' property.
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: none" \
+ "show style-1 foreground before changes"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: none" \
+ "show style-1 background before changes"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: normal" \
+ "show style-1 intensity before changes"
+
+ # Grab the gdb.Style objects from 'style-1'.
+ gdb_test_no_output "python s1 = style_1.style"
+ gdb_test_no_output "python s2 = style_1.value"
+
+ # Check both represent the same style.
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=none, bg=none, intensity=normal>" \
+ "print s1 style before changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=none, bg=none, intensity=normal>" \
+ "print s2 style before changes"
+
+ gdb_test_no_output \
+ "python s1.foreground=gdb.Color('red')" "set foreground"
+ gdb_test_no_output \
+ "python s1.background=gdb.Color('blue')" "set background"
+ gdb_test_no_output \
+ "python s1.intensity=gdb.INTENSITY_DIM" "set intensity"
+
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=red, bg=blue, intensity=dim>" \
+ "print s1 style after first set of changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=red, bg=blue, intensity=dim>" \
+ "print s2 style after first set of changes"
+
+ # Check the style properties have updated.
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: red" \
+ "show style-1 foreground after first set of changes"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: blue" \
+ "show style-1 background after first set of changes"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: dim" \
+ "show style-1 intensity after first set of changes"
+
+ # Change the style properties, check gdb.Style objects update.
+ gdb_test_no_output "set style style-1 foreground yellow"
+ gdb_test_no_output "set style style-1 background cyan"
+ gdb_test_no_output "set style style-1 intensity bold"
+
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=yellow, bg=cyan, intensity=bold>" \
+ "print s1 style after second set of changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=yellow, bg=cyan, intensity=bold>" \
+ "print s2 style after second set of changes"
+
+ # Assign a gdb.Style to set 'style-1'. First create some unnamed
+ # style objects that can be used.
+ gdb_test_no_output \
+ "python uns1 = gdb.Style(foreground=gdb.Color('white'), background=gdb.Color('black'), intensity=gdb.INTENSITY_BOLD)" \
+ "create uns1"
+ gdb_test_no_output \
+ "python uns2 = gdb.Style(foreground=gdb.Color('black'), background=gdb.Color('white'), intensity=gdb.INTENSITY_DIM)" \
+ "create uns2"
+
+ gdb_test_no_output "python style_1.value = uns1"
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: white" \
+ "show style-1 foreground after assigning uns1"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: black" \
+ "show style-1 background after assigning uns1"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: bold" \
+ "show style-1 intensity after assigning uns1"
+
+ gdb_test_no_output "python style_1.style = uns2"
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: black" \
+ "show style-1 foreground after assigning uns2"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: white" \
+ "show style-1 background after assigning uns2"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: dim" \
+ "show style-1 intensity after assigning uns2"
+
+ # Assign a style with an intensity that is not 'NORMAL' to a
+ # StyleParameterSet that doesn't have an intensity. The new
+ # intensity setting should be ignored.
+ gdb_test_no_output "python style_4.style = uns1"
+ gdb_test "show style style-4 foreground" \
+ "The \"style-4\" style foreground color is: white" \
+ "show style-4 foreground after assigning uns1"
+ gdb_test "show style style-4 background" \
+ "The \"style-4\" style background color is: black" \
+ "show style-4 background after assigning uns1"
+ gdb_test "show style style-4 intensity" \
+ "Undefined show style style-4 command: \"intensity\"\\. Try \"help show style style-4\"\\." \
+ "show style-4 intensity after assigning uns1"
+
+ gdb_test "python print(style_4.style)" \
+ "<gdb.Style name='style-4', fg=white, bg=black, intensity=normal>" \
+ "print string repr of style_4's style"
+}
+
+# Test creating a style prefix with gdb.ParameterPrefix, then adding
+# some styles within the new prefix. Change the style through the CLI
+# and confirm that the associated Python object updated as expected.
+proc_with_prefix test_style_prefix {} {
+ gdb_test_no_output \
+ "python gdb.ParameterPrefix(\"style my-style-group\", gdb.COMMAND_NONE)"
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"my-style-group style-1\")" \
+ "create style-1"
+ gdb_test_no_output \
+ "python style_2 = gdb.StyleParameterSet(\"my-style-group style-2\")" \
+ "create style-2"
+
+ gdb_test "python print(style_1.style)" \
+ "<gdb.Style name='my-style-group style-1', fg=none, bg=none, intensity=normal>" \
+ "print 'my-style-group style-1' style before changes"
+ gdb_test "python print(style_2.style)" \
+ "<gdb.Style name='my-style-group style-2', fg=none, bg=none, intensity=normal>" \
+ "print 'my-style-group style-2' style before changes"
+
+ gdb_test_no_output "set style my-style-group style-1 foreground red"
+ gdb_test_no_output "set style my-style-group style-1 background yellow"
+ gdb_test_no_output "set style my-style-group style-1 intensity bold"
+ gdb_test_no_output "set style my-style-group style-2 foreground black"
+ gdb_test_no_output "set style my-style-group style-2 background blue"
+ gdb_test_no_output "set style my-style-group style-2 intensity dim"
+
+ gdb_test "python print(style_1.style)" \
+ "<gdb.Style name='my-style-group style-1', fg=red, bg=yellow, intensity=bold>" \
+ "print 'my-style-group style-1' style after changes"
+ gdb_test "python print(style_2.style)" \
+ "<gdb.Style name='my-style-group style-2', fg=black, bg=blue, intensity=dim>" \
+ "print 'my-style-group style-2' style after changes"
+}
+
+# Test that gdb.StyleParameterSet.apply() works as expected.
+proc_with_prefix test_applying {} {
+ # Create a new StyleParameterSet, and adjust its settings.
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"style-1\")" \
+ "create style-1"
+ gdb_test_no_output \
+ "python uns1 = gdb.Style(foreground=gdb.Color('red'), background=gdb.Color('blue'), intensity=gdb.INTENSITY_BOLD)" \
+ "create uns1"
+ gdb_test_no_output "python style_1 = uns1"
+
+ # When styling is off (which it currently is), no escape sequences
+ # should be added.
+ gdb_test \
+ "python print(style_1.apply('xxx'))" "^xxx" \
+ "apply StyleParameterSet to a string when styling is off"
+
+ # When styling is on, we should see an escape sequence added.
+ gdb_test "with style enabled on -- python print(style_1.apply('xxx'))" \
+ "\033\\\[31;44;1;27mxxx\033\\\[m" \
+ "apply a style when styling is on"
+}
+
+# Start GDB.
+with_ansi_styling_terminal {
+ clean_restart
+}
+
+# Turn styling off so that the output of 'show style ...' isn't styled, this
+# makes it easier to match the output.
+gdb_test_no_output "set style enabled off"
+
+# Run the tests.
+test_basic_usage
+test_style_prefix
+test_applying
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv6 4/4] gdb/python: extend gdb.write to support styled output
2025-07-07 14:43 ` [PATCHv6 0/4] Python Style API Andrew Burgess
` (2 preceding siblings ...)
2025-07-07 14:43 ` [PATCHv6 3/4] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
@ 2025-07-07 14:43 ` Andrew Burgess
2025-08-13 10:25 ` [PATCHv7 0/4] Fix for gdb.Color + pagination AND new gdb.Style API Andrew Burgess
4 siblings, 0 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-07-07 14:43 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
It is already possible to produce styled output from Python by
converting the gdb.Style to its escape code sequence, and writing that
to the output stream.
But this commit adds an alternative option to the mix by extending the
existing gdb.write() function to accept a 'style' argument. The value
of this argument can be 'None' to indicate no style change should be
performed, this is the default, and matches the existing behaviour.
Or the new 'style' argument can be a gdb.Style object, in which case
the specified style is applied only for the string passed to
gdb.write, after which the default style is re-applied.
Using gdb.write with a style object more closely matches how GDB
handles styling internally, and has the benefit that the user doesn't
need to remember to restore the default style when they are done.
---
gdb/NEWS | 3 ++
gdb/doc/python.texi | 9 +++-
gdb/python/py-style.c | 27 ++++++++++
gdb/python/python-internal.h | 15 ++++++
gdb/python/python.c | 30 ++++++++++--
| 49 +++++++++++++++++++
| 16 ++++++
gdb/testsuite/gdb.python/py-style.exp | 29 +++++++++++
8 files changed, 172 insertions(+), 6 deletions(-)
diff --git a/gdb/NEWS b/gdb/NEWS
index 6ae6710e877..6a324706a1f 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -148,6 +148,9 @@ info threads [-gid] [-stopped] [-running] [ID]...
Use gdb.StyleParameterSet(NAME) to create 'set style NAME ...'
and 'show style NAME ...' parameters.
+ ** The gdb.write() function now takes an additional, optional,
+ 'style' argument, which can be used to style the output.
+
** 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 486f0a8f750..55a79b6785d 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -452,7 +452,7 @@ Basic Python
historical compatibility.
@end defun
-@defun gdb.write (string @r{[}, stream@r{]})
+@defun gdb.write (string @r{[}, stream@r{]} @r{[}, style@r{]})
Print a string to @value{GDBN}'s paginated output stream. The
optional @var{stream} determines the stream to print to. The default
stream is @value{GDBN}'s standard output stream. Possible stream
@@ -475,6 +475,13 @@ Basic Python
@value{GDBN}'s log stream (@pxref{Logging Output}).
@end table
+The @var{style} should be a @code{gdb.Style} object (@pxref{Styles In
+Python}), or @code{None} (the default). If @var{style} is @code{None}
+then the current style for @var{stream} will be applied to @var{text}.
+If @var{style} is a @code{gdb.Style} object, then this style is
+applied to @var{text}, after which the default output style is
+restored.
+
Writing to @code{sys.stdout} or @code{sys.stderr} will automatically
call this function and will automatically direct the output to the
relevant stream.
diff --git a/gdb/python/py-style.c b/gdb/python/py-style.c
index b3983af3a85..0e9af1fc4c4 100644
--- a/gdb/python/py-style.c
+++ b/gdb/python/py-style.c
@@ -353,6 +353,15 @@ stylepy_init (PyObject *self, PyObject *args, PyObject *kwargs)
\f
+/* See python-internal.h. */
+
+bool
+gdbpy_is_style (PyObject *obj)
+{
+ gdb_assert (obj != nullptr);
+ return PyObject_TypeCheck (obj, &style_object_type);
+}
+
/* Return the ui_file_style for STYLEPY. If the style cannot be found,
then return an empty optional, and set a Python error. */
@@ -369,6 +378,24 @@ stylepy_to_style (style_object *stylepy)
return style;
}
+/* See python-internal.h. */
+
+std::optional<ui_file_style>
+gdbpy_style_object_to_ui_file_style (PyObject *obj)
+{
+ gdb_assert (obj != nullptr);
+ if (!gdbpy_is_style (obj))
+ {
+ PyErr_Format
+ (PyExc_TypeError, _("argument must be a gdb.Style object, not %s."),
+ Py_TYPE (obj)->tp_name);
+ return {};
+ }
+
+ style_object *style_obj = (style_object *) obj;
+ return stylepy_to_style (style_obj);
+}
+
/* 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
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index 7f4237eecc2..28199691226 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -565,6 +565,21 @@ struct symtab_and_line *sal_object_to_symtab_and_line (PyObject *obj);
frame_info_ptr frame_object_to_frame_info (PyObject *frame_obj);
struct gdbarch *arch_object_to_gdbarch (PyObject *obj);
+/* Return true if OBJ is a gdb.Style object. OBJ must not be NULL. */
+
+extern bool gdbpy_is_style (PyObject *obj);
+
+/* Return the ui_file_style from OBJ, a gdb.Style object. OBJ must not be
+ NULL, but can be a non gdb.Style object, in which case a Python error is
+ set and an empty optional is returned.
+
+ It is also possible that OBJ is a gdb.Style object, but the underlying
+ style cannot be fetched for some reason. If this happens then a Python
+ error is set and an empty optional is returned. */
+
+extern std::optional<ui_file_style>
+ gdbpy_style_object_to_ui_file_style (PyObject *obj);
+
extern PyObject *gdbpy_execute_mi_command (PyObject *self, PyObject *args,
PyObject *kw);
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 1af7896eb08..77d96da61c6 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -1561,12 +1561,22 @@ static PyObject *
gdbpy_write (PyObject *self, PyObject *args, PyObject *kw)
{
const char *arg;
- static const char *keywords[] = { "text", "stream", NULL };
+ static const char *keywords[] = { "text", "stream", "style", nullptr };
int stream_type = 0;
+ PyObject *style_obj = Py_None;
- if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s|i", keywords, &arg,
- &stream_type))
- return NULL;
+ if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s|iO", keywords, &arg,
+ &stream_type, &style_obj))
+ return nullptr;
+
+ if (style_obj != Py_None && !gdbpy_is_style (style_obj))
+ {
+ PyErr_Format
+ (PyExc_TypeError,
+ _("'style' argument must be gdb.Style or None, not %s."),
+ Py_TYPE (style_obj)->tp_name);
+ return nullptr;
+ }
try
{
@@ -1584,7 +1594,17 @@ gdbpy_write (PyObject *self, PyObject *args, PyObject *kw)
break;
}
- gdb_puts (arg, stream);
+ if (style_obj == Py_None)
+ gdb_puts (arg, stream);
+ else
+ {
+ std::optional<ui_file_style> style
+ = gdbpy_style_object_to_ui_file_style (style_obj);
+ if (!style.has_value ())
+ return nullptr;
+
+ fputs_styled (arg, style.value (), stream);
+ }
}
catch (const gdb_exception &except)
{
--git a/gdb/testsuite/gdb.python/py-color-pagination.exp b/gdb/testsuite/gdb.python/py-color-pagination.exp
index 7b3dc1859c1..0a2f01e6533 100644
--- a/gdb/testsuite/gdb.python/py-color-pagination.exp
+++ b/gdb/testsuite/gdb.python/py-color-pagination.exp
@@ -110,8 +110,57 @@ proc test_pagination { type mode } {
}
}
+# Run the command 'style-fill-v2' which fills the screen with output and
+# triggers the pagination prompt. Check that styling is applied correctly
+# to the output. This v2 command is exercising passing a style to
+# gdb.write() rather than passing the escape sequence for the style.
+proc test_pagination_v2 { } {
+ set saw_bad_color_handling false
+ set expected_restore_color ""
+ set last_color ""
+ gdb_test_multiple "style-fill-v2" "" {
+ -re "^style-fill-v2\r\n" {
+ exp_continue
+ }
+
+ -re "^((?:${::any_color}\033\\\[m)?)(${::any_color})$::str\033\\\[m" {
+ # After a continuation prompt GDB will restore the previous
+ # color, and then we immediately switch to a new color.
+ set restored_color $expect_out(1,string)
+ if { $restored_color ne ""
+ && $restored_color ne $expected_restore_color } {
+ set saw_bad_color_handling true
+ }
+ set last_color $expect_out(2,string)
+ exp_continue
+ }
+
+ -re "^$::pagination_prompt$" {
+ # After a pagination prompt we expect GDB to restore the last
+ # color, but this will then be disabled due to a styled
+ # gdb.write emitting a return to default style escape sequence.
+ set expected_restore_color "$last_color\033\[m"
+
+ # Send '\n' to view more output.
+ send_gdb "\n"
+ exp_continue
+ }
+
+ -re "^\r\n" {
+ # The matches the newline sent to the continuation prompt.
+ exp_continue
+ }
+
+ -re "^$::gdb_prompt $" {
+ gdb_assert { !$saw_bad_color_handling } $gdb_test_name
+ }
+ }
+}
+
foreach_with_prefix type { color style } {
foreach_with_prefix mode { write print } {
test_pagination $type $mode
}
}
+
+test_pagination_v2
--git a/gdb/testsuite/gdb.python/py-color-pagination.py b/gdb/testsuite/gdb.python/py-color-pagination.py
index f0252e5bf86..9cdc76cb0ce 100644
--- a/gdb/testsuite/gdb.python/py-color-pagination.py
+++ b/gdb/testsuite/gdb.python/py-color-pagination.py
@@ -62,5 +62,21 @@ class StyleTester(gdb.Command):
write(mode, "\n")
+class StyleTester2(gdb.Command):
+ def __init__(self):
+ super().__init__("style-fill-v2", gdb.COMMAND_USER)
+
+ def invoke(self, args, from_tty):
+ str = "<" + "-" * 78 + ">"
+ for i in range(0, 20):
+ for color_name in basic_colors:
+ c = gdb.Color(color_name)
+ s = gdb.Style(foreground=c)
+ gdb.write(str, style=s)
+
+ gdb.write("\n")
+
+
ColorTester()
StyleTester()
+StyleTester2()
diff --git a/gdb/testsuite/gdb.python/py-style.exp b/gdb/testsuite/gdb.python/py-style.exp
index 020d0e0319b..7f3b01333a1 100644
--- a/gdb/testsuite/gdb.python/py-style.exp
+++ b/gdb/testsuite/gdb.python/py-style.exp
@@ -340,3 +340,32 @@ gdb_test_no_output "python input_text = \"a totally different string that is als
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"
+
+# Test gdb.write passing in a style. Define a helper function to
+# ensure all output is flushed before we return to the prompt.
+gdb_test_multiline "create function to call gdb.write then flush" \
+ "python" "" \
+ "def write_and_flush(*args, **kwargs):" "" \
+ " gdb.write(*args, **kwargs)" "" \
+ " gdb.write(\"\\n\")" "" \
+ " gdb.flush(gdb.STDOUT)" "" \
+ "end" ""
+
+gdb_test "python write_and_flush(\"some text\")" \
+ "^some text" "unstyled text, no style passed"
+
+gdb_test "python write_and_flush(\"some text\", style=None)" \
+ "^some text" "unstyled text, pass style as None"
+
+gdb_test "python write_and_flush(\"some text\", style=filename_style)" \
+ "^\033\\\[34;41;2;27msome text\033\\\[m" \
+ "styled output, pass style by keyword"
+
+gdb_test "python write_and_flush(\"some text\", gdb.STDOUT, filename_style)" \
+ "^\033\\\[34;41;2;27msome text\033\\\[m" \
+ "styled output, pass style by position"
+
+gdb_test "python write_and_flush(\"some text\", style='filename')" \
+ [multi_line \
+ "Python Exception <class 'TypeError'>: 'style' argument must be gdb\\.Style or None, not str\\." \
+ "Error occurred in Python: 'style' argument must be gdb\\.Style or None, not str\\."]
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv7 0/4] Fix for gdb.Color + pagination AND new gdb.Style API
2025-07-07 14:43 ` [PATCHv6 0/4] Python Style API Andrew Burgess
` (3 preceding siblings ...)
2025-07-07 14:43 ` [PATCHv6 4/4] gdb/python: extend gdb.write to support styled output Andrew Burgess
@ 2025-08-13 10:25 ` Andrew Burgess
2025-08-13 10:25 ` [PATCHv7 1/4] gdb: allow gdb.Color to work correctly with pagination Andrew Burgess
` (4 more replies)
4 siblings, 5 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-08-13 10:25 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
Patch #1 in this series can be reviewed and approved separately from
the other 3 patches if needed. It contains a fix for using the
gdb.Color API within custom GDB commands where pagination might be
triggered. This fix is needed by the later 3 patches though, which is
why they are all bundled together.
The following text really describes the motivation for the last 3
patches in this series:
After the gdb.Color support was added to the Python API I wondered if
this would be enough to easily add styling to custom Python commands.
But I don't think it is, there's still lots that would need to be
handled in user code, for example, the intensity setting. And having
styles that track named styles (e.g. if I want to apply the 'filename'
style to some text from Python).
So this series builds on top of the gdb.Color API to provide a new
gdb.Style API which, I hope, makes it easy to start writing Python
commands that can use GDB's styles.
Here's an example of a simple custome command that uses styles:
import os
class user_home_cmd(gdb.Command):
def __init__(self):
super().__init__("user-home", gdb.COMMAND_USER)
self._style = gdb.Style('filename')
def invoke(self, args, from_tty):
print("The filename is %s."
% (self._style.apply(os.environ['HOME'])))
user_home_cmd()
The new 'user-home' command prints the current users home directory
using 'filename' style. Changing the 'filename' style and re-running
the command will track the style changes.
In v2:
- Fixed an issue with the py-style-parameter-set.exp test; GDB was
not started in a terminal with styling support in one case, so
tests would fail depending on how the testsuite was being run.
In v3:
- Another test issue. This time, the error message given when
parsing an argument of the wrong type changes with Python version.
Relax the regexp to accept any 'TypeError' message.
In v4:
- Two new patches. First patch extends pager_file so that
gdb.Color, and gdb.Style when it's added can work well with GDB's
pager. Last patch extends the gdb.write() Python function to also
support styling.
- I've renamed some of the classes in python/py-style.c, nothing
major, I replaced 'stylepy' with 'style' in some names. I think
the new names are clearer, and better match the rest of GDB's
Python API code.
- Unfortunately, the new pager_file changes mean that this patch
series now depends on this other patch:
https://inbox.sourceware.org/gdb-patches/444008aeae2bb3c68cf868fa317374b3d7973860.1750197766.git.aburgess@redhat.com
Without that patch there will be some failures in
gdb.python/py-color-pagination.exp. I'm not proposing to merge
this until that other patch, or something like it, is merged. But
this series could be reviewed independently.
In v5:
- The pager_file styling fixes mentioned for v4 have now been merged
to master. I've rebased this series on top of them, and all the
tests are now passing.
- I've tweaked the wording for a couple of the commit messages just
to make things clearer.
- No real code changes.
In v6:
- Rebased as there were some style related changes merged to master
recently. No conflicts, and everything still works fine, so there
are no real changes here.
In v7:
- Rebased as there were more style related changes to master which
caused some merged conflicts, and test failures. Both turned out
to be trivial to fix.
Thanks,
Andrew
---
Andrew Burgess (4):
gdb: allow gdb.Color to work correctly with pagination
gdb/python: add gdb.Style class
gdb/python: new class gdb.StyleParameterSet
gdb/python: extend gdb.write to support styled output
gdb/Makefile.in | 1 +
gdb/NEWS | 14 +
gdb/doc/python.texi | 262 +++++-
gdb/python/lib/gdb/__init__.py | 209 +++++
gdb/python/py-style.c | 824 ++++++++++++++++++
gdb/python/python-internal.h | 15 +
gdb/python/python.c | 46 +-
.../gdb.python/py-color-pagination.exp | 172 ++++
.../gdb.python/py-color-pagination.py | 82 ++
.../gdb.python/py-style-parameter-set.exp | 366 ++++++++
gdb/testsuite/gdb.python/py-style.exp | 371 ++++++++
gdb/ui-style.h | 6 +
gdb/utils.c | 21 +-
13 files changed, 2372 insertions(+), 17 deletions(-)
create mode 100644 gdb/python/py-style.c
create mode 100644 gdb/testsuite/gdb.python/py-color-pagination.exp
create mode 100644 gdb/testsuite/gdb.python/py-color-pagination.py
create mode 100644 gdb/testsuite/gdb.python/py-style-parameter-set.exp
create mode 100644 gdb/testsuite/gdb.python/py-style.exp
base-commit: 97b6ffe44b98479f991ab38b4c59f4fd0f30198e
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv7 1/4] gdb: allow gdb.Color to work correctly with pagination
2025-08-13 10:25 ` [PATCHv7 0/4] Fix for gdb.Color + pagination AND new gdb.Style API Andrew Burgess
@ 2025-08-13 10:25 ` Andrew Burgess
2025-08-24 16:13 ` Andrew Burgess
2025-08-13 10:29 ` [PATCHv7 2/4] gdb/python: add gdb.Style class Andrew Burgess
` (3 subsequent siblings)
4 siblings, 1 reply; 61+ messages in thread
From: Andrew Burgess @ 2025-08-13 10:25 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
This commit allows gdb.Color objects to be used to style output from
GDB commands written in Python, and the styled output should work
correctly with pagination.
There are two parts to fixing this:
First, GDB needs to be able to track the currently applied style
within the page_file class. This means that style changes need to be
achieved with calls to pager_file::emit_style_escape.
Now usually, GDB does this by calling something like fprintf_styled,
which takes care to apply the style for us. However, that's not
really an option here as a gdb.Color isn't a full style, and as the
gdb.Color object is designed to be converted directly into escape
sequences that can then be printed, we really need a solution that
works with this approach.
However pager_file::puts already has code in place to handle escape
sequences. Right now all this code does is spot the escape sequence
and append it to the m_wrap_buffer. But in this commit I propose that
we go one step further, parse the escape sequence back into a
ui_file_style object in pager_file::puts, and then we can call
pager_file::emit_style_escape.
If the parsing doesn't work then we can just add the escape sequence
to m_wrap_buffer as we did before.
But wait, how can this work if a gdb.Color isn't a full style? Turns
out that's not a problem. We only ever emit the escape sequence for
those parts of a style that need changing, so a full style that sets
the foreground color will emit the same escape sequence as a gdb.Color
for the foreground. When we convert the escape sequence back into a
ui_file_style, then we get a style with everything set to default,
except the foreground color.
I had hoped that this would be all that was needed. But unfortunately
this doesn't work because of the second problem...
... the implementation of the Python function gdb.write() calls
gdb_printf(), which calls gdb_vprintf(), which calls ui_file::vprintf,
which calls ui_out::vmessage, which calls ui_out::call_do_message, and
finally we reach cli_ui_out::do_message. This final do_message
function does this:
ui_file *stream = m_streams.back ();
stream->emit_style_escape (style);
stream->puts (str.c_str ());
stream->emit_style_escape (ui_file_style ());
If we imagine the case where we are emitting a style, triggered from
Python like this:
gdb.write(gdb.Color('red').escape_sequence(True))
the STYLE in this case will be the default ui_file_style(), and STR
will hold the escape sequence we are writing.
After the first change, where pager_file::puts now calls
pager_file::emit_style_escape, the current style of STREAM will have
been updated. But this means that the final emit_style_escape will
now restore the default style.
The fix for this is to avoid using the high level gdb_printf from
gdb.write(), and instead use gdb_puts instead. The gdb_puts function
doesn't restore the default style, which means our style modification
survives.
There's a new test included. This test includes what appears like a
pointless extra loop (looping over a single value), but later commits
in this series will add more values to this list.
---
gdb/python/python.c | 18 +--
| 122 ++++++++++++++++++
| 46 +++++++
gdb/utils.c | 21 ++-
4 files changed, 195 insertions(+), 12 deletions(-)
create mode 100644 gdb/testsuite/gdb.python/py-color-pagination.exp
create mode 100644 gdb/testsuite/gdb.python/py-color-pagination.py
diff --git a/gdb/python/python.c b/gdb/python/python.c
index cb0d642a67d..1af7896eb08 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -1570,21 +1570,21 @@ gdbpy_write (PyObject *self, PyObject *args, PyObject *kw)
try
{
+ ui_file *stream;
switch (stream_type)
{
case 1:
- {
- gdb_printf (gdb_stderr, "%s", arg);
- break;
- }
+ stream = gdb_stderr;
+ break;
case 2:
- {
- gdb_printf (gdb_stdlog, "%s", arg);
- break;
- }
+ stream = gdb_stdlog;
+ break;
default:
- gdb_printf (gdb_stdout, "%s", arg);
+ stream = gdb_stdout;
+ break;
}
+
+ gdb_puts (arg, stream);
}
catch (const gdb_exception &except)
{
--git a/gdb/testsuite/gdb.python/py-color-pagination.exp b/gdb/testsuite/gdb.python/py-color-pagination.exp
new file mode 100644
index 00000000000..3235fffe5cc
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-color-pagination.exp
@@ -0,0 +1,122 @@
+# 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.Color and how this
+# interacts with GDB's pagination system.
+
+load_lib gdb-python.exp
+
+require allow_python_tests
+
+standard_testfile
+
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+
+set str "<[string repeat - 78]>"
+
+# These define all the default attributes for a style: background
+# color, intensity, italics, and underlined.
+set other_attr ";49;22;23;24;27"
+
+# These colors set the foreground color only. Everything else is the
+# default.
+set black "(?:\033\\\[30${other_attr}m)"
+set red "(?:\033\\\[31${other_attr}m)"
+set green "(?:\033\\\[32${other_attr}m)"
+set yellow "(?:\033\\\[33${other_attr}m)"
+set blue "(?:\033\\\[34${other_attr}m)"
+set magenta "(?:\033\\\[35${other_attr}m)"
+set cyan "(?:\033\\\[36${other_attr}m)"
+set white "(?:\033\\\[37${other_attr}m)"
+
+set any_color "(?:${black}|${red}|${green}|${yellow}|${blue}|${magenta}|${cyan}|${white})"
+
+# Run the command 'TYPE-fill MODE' which fills the screen with output and
+# triggers the pagination prompt. Check that styling is applied correctly
+# to the output.
+proc test_pagination { type mode } {
+
+ # Start with a fresh GDB, but enable color support.
+ with_ansi_styling_terminal {
+ clean_restart
+ }
+
+ gdb_test_no_output "source $::pyfile" "source the script"
+
+ gdb_test_no_output "set width 80"
+ gdb_test_no_output "set height 15"
+
+ set saw_bad_color_handling false
+ set expected_restore_color ""
+ set last_color ""
+ gdb_test_multiple "$type-fill $mode" "" {
+ -re "^$type-fill $mode\r\n" {
+ exp_continue
+ }
+
+ -re "^(${::any_color}?)(${::any_color})$::str" {
+ # After a continuation prompt GDB will restore the previous
+ # color, and then we immediately switch to a new color.
+ set restored_color $expect_out(1,string)
+ if { $restored_color ne ""
+ && $restored_color ne $expected_restore_color } {
+ set saw_bad_color_handling true
+ }
+ set last_color $expect_out(2,string)
+ exp_continue
+ }
+
+ -re "^\033\\\[${::decimal}m$::str" {
+ # This catches the case where the color's escape sequence has
+ # not been converted back into a full style. This indicates
+ # something went wrong in the pager_file::puts function.
+ set saw_bad_color_handling true
+ exp_continue
+ }
+
+ -re "^((?:\033\\\[m)?)$::pagination_prompt$" {
+ # After a pagination prompt we expect GDB to restore the last
+ # color.
+ set expected_restore_color $last_color
+
+ # If we didn't see a color reset sequence then the pagination
+ # prompt will have been printed in the wrong color, this is a
+ # GDB bug.
+ set color_reset $expect_out(1,string)
+ if { $color_reset eq "" } {
+ set saw_bad_color_handling true
+ }
+
+ # Send '\n' to view more output.
+ send_gdb "\n"
+ exp_continue
+ }
+
+ -re "^\r\n" {
+ # The matches the newline sent to the continuation prompt.
+ exp_continue
+ }
+
+ -re "^\033\\\[m\r\n$::gdb_prompt $" {
+ gdb_assert { !$saw_bad_color_handling } $gdb_test_name
+ }
+ }
+}
+
+foreach_with_prefix type { color } {
+ foreach_with_prefix mode { write print } {
+ test_pagination $type $mode
+ }
+}
--git a/gdb/testsuite/gdb.python/py-color-pagination.py b/gdb/testsuite/gdb.python/py-color-pagination.py
new file mode 100644
index 00000000000..efd501eedf5
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-color-pagination.py
@@ -0,0 +1,46 @@
+# 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/>.
+
+import gdb
+
+basic_colors = ["black", "red", "green", "yellow", "blue", "magenta", "cyan", "white"]
+
+
+def write(mode, text):
+ if mode == "write":
+ gdb.write(text)
+ else:
+ print(text, end="")
+
+
+class ColorTester(gdb.Command):
+ def __init__(self):
+ super().__init__("color-fill", gdb.COMMAND_USER)
+
+ def invoke(self, args, from_tty):
+ mode = args
+ str = "<" + "-" * 78 + ">"
+ for i in range(0, 20):
+ for color_name in basic_colors:
+ c = gdb.Color(color_name)
+ write(mode, c.escape_sequence(True))
+ write(mode, str)
+
+ default = gdb.Color("none")
+ write(mode, default.escape_sequence(True))
+ write(mode, "\n")
+
+
+ColorTester()
diff --git a/gdb/utils.c b/gdb/utils.c
index 10d3d51e481..92e626a9c75 100644
--- a/gdb/utils.c
+++ b/gdb/utils.c
@@ -1702,10 +1702,25 @@ pager_file::puts (const char *linebuffer)
else if (*linebuffer == '\033'
&& skip_ansi_escape (linebuffer, &skip_bytes))
{
- m_wrap_buffer.append (linebuffer, skip_bytes);
- /* Note that we don't consider this a character, so we
+ /* We don't consider escape sequences as characters, so we
don't increment chars_printed here. */
- linebuffer += skip_bytes;
+
+ size_t style_len;
+ ui_file_style style;
+ if (style.parse (linebuffer, &style_len)
+ && style_len <= skip_bytes)
+ {
+ this->emit_style_escape (style);
+
+ linebuffer += style_len;
+ skip_bytes -= style_len;
+ }
+
+ if (skip_bytes > 0)
+ {
+ m_wrap_buffer.append (linebuffer, skip_bytes);
+ linebuffer += skip_bytes;
+ }
}
else if (*linebuffer == '\r')
{
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv7 2/4] gdb/python: add gdb.Style class
2025-08-13 10:25 ` [PATCHv7 0/4] Fix for gdb.Color + pagination AND new gdb.Style API Andrew Burgess
2025-08-13 10:25 ` [PATCHv7 1/4] gdb: allow gdb.Color to work correctly with pagination Andrew Burgess
@ 2025-08-13 10:29 ` Andrew Burgess
2025-08-13 10:30 ` [PATCHv7 4/4] gdb/python: extend gdb.write to support styled output Andrew Burgess
` (2 subsequent siblings)
4 siblings, 0 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-08-13 10:29 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess, Eli Zaretskii
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 ++++++++++++++++++
| 5 +-
| 20 +
gdb/testsuite/gdb.python/py-style.exp | 342 ++++++++
gdb/ui-style.h | 6 +
8 files changed, 1316 insertions(+), 2 deletions(-)
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 fc0c56564c2..d6950d45453 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 4c9aed421bb..303374377ba 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -137,6 +137,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 6fa22851f6a..0b4ed9d6a1c 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.
@@ -7262,6 +7263,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..b3983af3a85
--- /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 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;
+
+ 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)
+{
+ 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;
+ (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)
+{
+ 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);
--git a/gdb/testsuite/gdb.python/py-color-pagination.exp b/gdb/testsuite/gdb.python/py-color-pagination.exp
index 3235fffe5cc..6a1596c2b8c 100644
--- a/gdb/testsuite/gdb.python/py-color-pagination.exp
+++ b/gdb/testsuite/gdb.python/py-color-pagination.exp
@@ -14,7 +14,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# This file is part of the GDB testsuite. It tests gdb.Color and how this
-# interacts with GDB's pagination system.
+# interacts with GDB's pagination system. The test also tests gdb.Style
+# because the tests are very similar.
load_lib gdb-python.exp
@@ -115,7 +116,7 @@ proc test_pagination { type mode } {
}
}
-foreach_with_prefix type { color } {
+foreach_with_prefix type { color style } {
foreach_with_prefix mode { write print } {
test_pagination $type $mode
}
--git a/gdb/testsuite/gdb.python/py-color-pagination.py b/gdb/testsuite/gdb.python/py-color-pagination.py
index efd501eedf5..f0252e5bf86 100644
--- a/gdb/testsuite/gdb.python/py-color-pagination.py
+++ b/gdb/testsuite/gdb.python/py-color-pagination.py
@@ -43,4 +43,24 @@ class ColorTester(gdb.Command):
write(mode, "\n")
+class StyleTester(gdb.Command):
+ def __init__(self):
+ super().__init__("style-fill", gdb.COMMAND_USER)
+
+ def invoke(self, args, from_tty):
+ mode = args
+ str = "<" + "-" * 78 + ">"
+ for i in range(0, 20):
+ for color_name in basic_colors:
+ c = gdb.Color(color_name)
+ s = gdb.Style(foreground=c)
+ write(mode, s.escape_sequence())
+ write(mode, str)
+
+ default = gdb.Style()
+ write(mode, default.escape_sequence())
+ write(mode, "\n")
+
+
ColorTester()
+StyleTester()
diff --git a/gdb/testsuite/gdb.python/py-style.exp b/gdb/testsuite/gdb.python/py-style.exp
new file mode 100644
index 00000000000..b2efe9a97e3
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-style.exp
@@ -0,0 +1,342 @@
+# 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"
diff --git a/gdb/ui-style.h b/gdb/ui-style.h
index 1ea3556869d..64f710b0cc1 100644
--- a/gdb/ui-style.h
+++ b/gdb/ui-style.h
@@ -334,6 +334,12 @@ struct ui_file_style
return m_intensity;
}
+ /* Set the intensity of this style. */
+ void set_intensity (intensity i)
+ {
+ m_intensity = i;
+ }
+
/* Return true if this style specified italic display; false
otherwise. */
bool is_italic () const
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv7 4/4] gdb/python: extend gdb.write to support styled output
2025-08-13 10:25 ` [PATCHv7 0/4] Fix for gdb.Color + pagination AND new gdb.Style API Andrew Burgess
2025-08-13 10:25 ` [PATCHv7 1/4] gdb: allow gdb.Color to work correctly with pagination Andrew Burgess
2025-08-13 10:29 ` [PATCHv7 2/4] gdb/python: add gdb.Style class Andrew Burgess
@ 2025-08-13 10:30 ` Andrew Burgess
2025-08-13 12:37 ` Eli Zaretskii
2025-08-13 10:34 ` [PATCHv7 3/4] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
2025-08-27 11:34 ` [PATCHv8 0/3] new gdb.Style API Andrew Burgess
4 siblings, 1 reply; 61+ messages in thread
From: Andrew Burgess @ 2025-08-13 10:30 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
It is already possible to produce styled output from Python by
converting the gdb.Style to its escape code sequence, and writing that
to the output stream.
But this commit adds an alternative option to the mix by extending the
existing gdb.write() function to accept a 'style' argument. The value
of this argument can be 'None' to indicate no style change should be
performed, this is the default, and matches the existing behaviour.
Or the new 'style' argument can be a gdb.Style object, in which case
the specified style is applied only for the string passed to
gdb.write, after which the default style is re-applied.
Using gdb.write with a style object more closely matches how GDB
handles styling internally, and has the benefit that the user doesn't
need to remember to restore the default style when they are done.
---
gdb/NEWS | 3 ++
gdb/doc/python.texi | 9 +++-
gdb/python/py-style.c | 27 ++++++++++
gdb/python/python-internal.h | 15 ++++++
gdb/python/python.c | 30 ++++++++++--
| 49 +++++++++++++++++++
| 16 ++++++
gdb/testsuite/gdb.python/py-style.exp | 29 +++++++++++
8 files changed, 172 insertions(+), 6 deletions(-)
diff --git a/gdb/NEWS b/gdb/NEWS
index 6ae6710e877..6a324706a1f 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -148,6 +148,9 @@ info threads [-gid] [-stopped] [-running] [ID]...
Use gdb.StyleParameterSet(NAME) to create 'set style NAME ...'
and 'show style NAME ...' parameters.
+ ** The gdb.write() function now takes an additional, optional,
+ 'style' argument, which can be used to style the output.
+
** 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 486f0a8f750..55a79b6785d 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -452,7 +452,7 @@ Basic Python
historical compatibility.
@end defun
-@defun gdb.write (string @r{[}, stream@r{]})
+@defun gdb.write (string @r{[}, stream@r{]} @r{[}, style@r{]})
Print a string to @value{GDBN}'s paginated output stream. The
optional @var{stream} determines the stream to print to. The default
stream is @value{GDBN}'s standard output stream. Possible stream
@@ -475,6 +475,13 @@ Basic Python
@value{GDBN}'s log stream (@pxref{Logging Output}).
@end table
+The @var{style} should be a @code{gdb.Style} object (@pxref{Styles In
+Python}), or @code{None} (the default). If @var{style} is @code{None}
+then the current style for @var{stream} will be applied to @var{text}.
+If @var{style} is a @code{gdb.Style} object, then this style is
+applied to @var{text}, after which the default output style is
+restored.
+
Writing to @code{sys.stdout} or @code{sys.stderr} will automatically
call this function and will automatically direct the output to the
relevant stream.
diff --git a/gdb/python/py-style.c b/gdb/python/py-style.c
index b3983af3a85..0e9af1fc4c4 100644
--- a/gdb/python/py-style.c
+++ b/gdb/python/py-style.c
@@ -353,6 +353,15 @@ stylepy_init (PyObject *self, PyObject *args, PyObject *kwargs)
\f
+/* See python-internal.h. */
+
+bool
+gdbpy_is_style (PyObject *obj)
+{
+ gdb_assert (obj != nullptr);
+ return PyObject_TypeCheck (obj, &style_object_type);
+}
+
/* Return the ui_file_style for STYLEPY. If the style cannot be found,
then return an empty optional, and set a Python error. */
@@ -369,6 +378,24 @@ stylepy_to_style (style_object *stylepy)
return style;
}
+/* See python-internal.h. */
+
+std::optional<ui_file_style>
+gdbpy_style_object_to_ui_file_style (PyObject *obj)
+{
+ gdb_assert (obj != nullptr);
+ if (!gdbpy_is_style (obj))
+ {
+ PyErr_Format
+ (PyExc_TypeError, _("argument must be a gdb.Style object, not %s."),
+ Py_TYPE (obj)->tp_name);
+ return {};
+ }
+
+ style_object *style_obj = (style_object *) obj;
+ return stylepy_to_style (style_obj);
+}
+
/* 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
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index 7f4237eecc2..28199691226 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -565,6 +565,21 @@ struct symtab_and_line *sal_object_to_symtab_and_line (PyObject *obj);
frame_info_ptr frame_object_to_frame_info (PyObject *frame_obj);
struct gdbarch *arch_object_to_gdbarch (PyObject *obj);
+/* Return true if OBJ is a gdb.Style object. OBJ must not be NULL. */
+
+extern bool gdbpy_is_style (PyObject *obj);
+
+/* Return the ui_file_style from OBJ, a gdb.Style object. OBJ must not be
+ NULL, but can be a non gdb.Style object, in which case a Python error is
+ set and an empty optional is returned.
+
+ It is also possible that OBJ is a gdb.Style object, but the underlying
+ style cannot be fetched for some reason. If this happens then a Python
+ error is set and an empty optional is returned. */
+
+extern std::optional<ui_file_style>
+ gdbpy_style_object_to_ui_file_style (PyObject *obj);
+
extern PyObject *gdbpy_execute_mi_command (PyObject *self, PyObject *args,
PyObject *kw);
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 1af7896eb08..77d96da61c6 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -1561,12 +1561,22 @@ static PyObject *
gdbpy_write (PyObject *self, PyObject *args, PyObject *kw)
{
const char *arg;
- static const char *keywords[] = { "text", "stream", NULL };
+ static const char *keywords[] = { "text", "stream", "style", nullptr };
int stream_type = 0;
+ PyObject *style_obj = Py_None;
- if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s|i", keywords, &arg,
- &stream_type))
- return NULL;
+ if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s|iO", keywords, &arg,
+ &stream_type, &style_obj))
+ return nullptr;
+
+ if (style_obj != Py_None && !gdbpy_is_style (style_obj))
+ {
+ PyErr_Format
+ (PyExc_TypeError,
+ _("'style' argument must be gdb.Style or None, not %s."),
+ Py_TYPE (style_obj)->tp_name);
+ return nullptr;
+ }
try
{
@@ -1584,7 +1594,17 @@ gdbpy_write (PyObject *self, PyObject *args, PyObject *kw)
break;
}
- gdb_puts (arg, stream);
+ if (style_obj == Py_None)
+ gdb_puts (arg, stream);
+ else
+ {
+ std::optional<ui_file_style> style
+ = gdbpy_style_object_to_ui_file_style (style_obj);
+ if (!style.has_value ())
+ return nullptr;
+
+ fputs_styled (arg, style.value (), stream);
+ }
}
catch (const gdb_exception &except)
{
--git a/gdb/testsuite/gdb.python/py-color-pagination.exp b/gdb/testsuite/gdb.python/py-color-pagination.exp
index 6a1596c2b8c..9c7e40900ab 100644
--- a/gdb/testsuite/gdb.python/py-color-pagination.exp
+++ b/gdb/testsuite/gdb.python/py-color-pagination.exp
@@ -116,8 +116,57 @@ proc test_pagination { type mode } {
}
}
+# Run the command 'style-fill-v2' which fills the screen with output and
+# triggers the pagination prompt. Check that styling is applied correctly
+# to the output. This v2 command is exercising passing a style to
+# gdb.write() rather than passing the escape sequence for the style.
+proc test_pagination_v2 { } {
+ set saw_bad_color_handling false
+ set expected_restore_color ""
+ set last_color ""
+ gdb_test_multiple "style-fill-v2" "" {
+ -re "^style-fill-v2\r\n" {
+ exp_continue
+ }
+
+ -re "^((?:${::any_color}\033\\\[m)?)(${::any_color})$::str\033\\\[m" {
+ # After a continuation prompt GDB will restore the previous
+ # color, and then we immediately switch to a new color.
+ set restored_color $expect_out(1,string)
+ if { $restored_color ne ""
+ && $restored_color ne $expected_restore_color } {
+ set saw_bad_color_handling true
+ }
+ set last_color $expect_out(2,string)
+ exp_continue
+ }
+
+ -re "^$::pagination_prompt$" {
+ # After a pagination prompt we expect GDB to restore the last
+ # color, but this will then be disabled due to a styled
+ # gdb.write emitting a return to default style escape sequence.
+ set expected_restore_color "$last_color\033\[m"
+
+ # Send '\n' to view more output.
+ send_gdb "\n"
+ exp_continue
+ }
+
+ -re "^\r\n" {
+ # The matches the newline sent to the continuation prompt.
+ exp_continue
+ }
+
+ -re "^$::gdb_prompt $" {
+ gdb_assert { !$saw_bad_color_handling } $gdb_test_name
+ }
+ }
+}
+
foreach_with_prefix type { color style } {
foreach_with_prefix mode { write print } {
test_pagination $type $mode
}
}
+
+test_pagination_v2
--git a/gdb/testsuite/gdb.python/py-color-pagination.py b/gdb/testsuite/gdb.python/py-color-pagination.py
index f0252e5bf86..9cdc76cb0ce 100644
--- a/gdb/testsuite/gdb.python/py-color-pagination.py
+++ b/gdb/testsuite/gdb.python/py-color-pagination.py
@@ -62,5 +62,21 @@ class StyleTester(gdb.Command):
write(mode, "\n")
+class StyleTester2(gdb.Command):
+ def __init__(self):
+ super().__init__("style-fill-v2", gdb.COMMAND_USER)
+
+ def invoke(self, args, from_tty):
+ str = "<" + "-" * 78 + ">"
+ for i in range(0, 20):
+ for color_name in basic_colors:
+ c = gdb.Color(color_name)
+ s = gdb.Style(foreground=c)
+ gdb.write(str, style=s)
+
+ gdb.write("\n")
+
+
ColorTester()
StyleTester()
+StyleTester2()
diff --git a/gdb/testsuite/gdb.python/py-style.exp b/gdb/testsuite/gdb.python/py-style.exp
index b2efe9a97e3..491e189774e 100644
--- a/gdb/testsuite/gdb.python/py-style.exp
+++ b/gdb/testsuite/gdb.python/py-style.exp
@@ -340,3 +340,32 @@ gdb_test_no_output "python input_text = \"a totally different string that is als
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"
+
+# Test gdb.write passing in a style. Define a helper function to
+# ensure all output is flushed before we return to the prompt.
+gdb_test_multiline "create function to call gdb.write then flush" \
+ "python" "" \
+ "def write_and_flush(*args, **kwargs):" "" \
+ " gdb.write(*args, **kwargs)" "" \
+ " gdb.write(\"\\n\")" "" \
+ " gdb.flush(gdb.STDOUT)" "" \
+ "end" ""
+
+gdb_test "python write_and_flush(\"some text\")" \
+ "^some text" "unstyled text, no style passed"
+
+gdb_test "python write_and_flush(\"some text\", style=None)" \
+ "^some text" "unstyled text, pass style as None"
+
+gdb_test "python write_and_flush(\"some text\", style=filename_style)" \
+ "^\033\\\[34;41;2;23;24;27msome text\033\\\[m" \
+ "styled output, pass style by keyword"
+
+gdb_test "python write_and_flush(\"some text\", gdb.STDOUT, filename_style)" \
+ "^\033\\\[34;41;2;23;24;27msome text\033\\\[m" \
+ "styled output, pass style by position"
+
+gdb_test "python write_and_flush(\"some text\", style='filename')" \
+ [multi_line \
+ "Python Exception <class 'TypeError'>: 'style' argument must be gdb\\.Style or None, not str\\." \
+ "Error occurred in Python: 'style' argument must be gdb\\.Style or None, not str\\."]
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv7 3/4] gdb/python: new class gdb.StyleParameterSet
2025-08-13 10:25 ` [PATCHv7 0/4] Fix for gdb.Color + pagination AND new gdb.Style API Andrew Burgess
` (2 preceding siblings ...)
2025-08-13 10:30 ` [PATCHv7 4/4] gdb/python: extend gdb.write to support styled output Andrew Burgess
@ 2025-08-13 10:34 ` Andrew Burgess
2025-08-13 13:05 ` Eli Zaretskii
2025-08-13 13:05 ` Eli Zaretskii
2025-08-27 11:34 ` [PATCHv8 0/3] new gdb.Style API Andrew Burgess
4 siblings, 2 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-08-13 10:34 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
Add a new helper class gdb.StyleParameterSet. This new class can be
used to simplify creation of new style parameter sets. A style
parameter set is the 'foreground', 'background', and (optionally), the
'intensity' settings, all grouped under a single prefix command.
And example usage is:
(gdb) python s = gdb.StyleParameterSet("my-style")
(gdb) show style my-style
style my-style background: The "my-style" style background color is: none
style my-style foreground: The "my-style" style foreground color is: none
style my-style intensity: The "my-style" style display intensity is: normal
(gdb)
Having created a gdb.StyleParameterSet, the object itself can be used
to access a named style corresponding to the setting group, like this:
(gdb) python print(s.style)
<gdb.Style name='my-style', fg=none, bg=none, intensity=normal>
(gdb)
Of course, having access to the gdb.Style makes it easy to change the
settings, or the settings can be adjusted via the normal CLI 'set'
commands.
As gdb.StyleParameterSet manages a set of parameters, and the
gdb.Parameter class uses Parameter.value as the attribute to read the
parameter's value, there is also StyleParameterSet.value, but this is
just an alias for StyleParameterSet.style, that is, it allows the
gdb.Style object to be read and written too.
It is worth noting that this class only creates a single level of
prefix command. As an example GDB has style 'disassembler mnemonic',
where the 'disassembler' part is a group of related styles. If a user
wanted to create:
style
my-style-group
style-1
style-2
style-3
Where each of 'style-1', 'style-2', and 'style-3' will have the full
set of 'foreground', 'background', and 'intensity', then the
gdb.StyleParameterSet can be used to create the 'style-N' part, but
the user will have to create the 'my-style-group' prefix themselves,
possibly using gdb.ParameterPrefix, e.g.:
gdb.ParameterPrefix("style my-style-group", gdb.COMMAND_NONE)
gdb.StyleParameterSet("my-style-group style-1")
gdb.StyleParameterSet("my-style-group style-2")
gdb.StyleParameterSet("my-style-group style-3")
---
gdb/NEWS | 4 +
gdb/doc/python.texi | 115 +++++-
gdb/python/lib/gdb/__init__.py | 209 ++++++++++
.../gdb.python/py-style-parameter-set.exp | 366 ++++++++++++++++++
4 files changed, 693 insertions(+), 1 deletion(-)
create mode 100644 gdb/testsuite/gdb.python/py-style-parameter-set.exp
diff --git a/gdb/NEWS b/gdb/NEWS
index 303374377ba..6ae6710e877 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -144,6 +144,10 @@ info threads [-gid] [-stopped] [-running] [ID]...
gdb.INTENSITY_DIM for use with gdb.Style when representing
intensities.
+ ** New gdb.StyleParameterSet for creating custom style settings.
+ Use gdb.StyleParameterSet(NAME) to create 'set style NAME ...'
+ and 'show style NAME ...' parameters.
+
** 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 0b4ed9d6a1c..486f0a8f750 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -5293,6 +5293,7 @@ Parameters In Python
feature-2}, then the @kbd{plugin-name} would need to be a prefix
command (@pxref{CLI Commands In Python}).
+@anchor{gdb.ParameterPrefix}
However, when creating parameters, you will almost always need to
create two prefix commands, one as a @kbd{set} sub-command, and one as
a @kbd{show} sub-command. @value{GDBN} provides the
@@ -5379,6 +5380,117 @@ Parameters In Python
ExampleParam("plugin-name feature-2")
@end smallexample
+@anchor{Creating Style Parameters}
+The helper class @code{gdb.StyleParameterSet} exists to make it easier
+to create new styles. @value{GDBN} places style settings under
+@samp{show style @dots{}} and @samp{set style @dots{}}, an example of a style
+is @samp{filename}. Each style is really a prefix command (@pxref{CLI
+Commands In Python}), with sub-commands @samp{foreground},
+@samp{background}, and optionally, @samp{intensity}.
+
+It is simple enough to create a new style using two @code{gdb.Command}
+objects for the prefix commands (one for @samp{set}, and one for
+@samp{show}), and three @code{gdb.Parameter} objects, one each for the
+@samp{foreground}, @samp{background}, and @samp{intensity}. You would
+also want to take care to craft the help text so that the new style
+behaves the same as the existing styles.
+
+Or, you can use the @code{gdb.StyleParameterSet} class, which takes
+care of all this, as the following example shows:
+
+@smallexample
+@group
+(@value{GDBP}) python s = gdb.StyleParameterSet("my-style")
+(@value{GDBP}) show style my-style
+style my-style background: The "my-style" style background color is: none
+style my-style foreground: The "my-style" style foreground color is: none
+style my-style intensity: The "my-style" style display intensity is: normal
+(@value{GDBP})
+@end group
+@end smallexample
+
+You might also want to group a number of styles within a new prefix,
+similar to how @value{GDBN} groups disassembler related styles within
+the @samp{style disassembler} prefix. This can be done using
+@code{gdb.ParameterPrefix} (@pxref{gdb.ParameterPrefix}), as in this
+example:
+
+@smallexample
+@group
+(@value{GDBP}) python gdb.ParameterPrefix("style my-group", gdb.COMMAND_NONE)
+(@value{GDBP}) python s_a = gdb.StyleParameterSet("my-group aaa")
+(@value{GDBP}) python s_b = gdb.StyleParameterSet("my-group bbb")
+(@value{GDBP}) show style my-group
+style my-group aaa background: The "my-group aaa" style background color is: none
+style my-group aaa foreground: The "my-group aaa" style foreground color is: none
+style my-group aaa intensity: The "my-group aaa" style display intensity is: normal
+style my-group bbb background: The "my-group bbb" style background color is: none
+style my-group bbb foreground: The "my-group bbb" style foreground color is: none
+style my-group bbb intensity: The "my-group bbb" style display intensity is: normal
+(@value{GDBP})
+@end group
+@end smallexample
+
+The @code{gdb.StyleParameterSet} class has the following methods and
+attributes:
+
+@defun StyleParameterSet.__init__(name, @w{add_intensity=@code{True}}, @w{doc=@code{None}})
+Create a new style group based on @var{name}, which is a string. For
+example if @var{name} is @samp{my-style}, then @value{GDBN} will
+create the prefix commands @samp{set style my-style} and @samp{show
+style my-style}. Within these prefix commands will be
+@samp{foreground}, @samp{background}, and @samp{intensity} parameters
+with the appropriate types.
+
+It is also possible for @var{name} to consist of multiple words, so
+long as each prefix command (except the last one) already exists. For
+example, it is valid to use a @var{name} value of @samp{disassembler
+my-style}, as the @samp{disassembler} prefix command already exists.
+@value{GDBN} would then create @samp{set style disassembler my-style}
+and @samp{show style disassembler my-style}, and within the
+@samp{my-style} prefixes will be the @samp{foreground},
+@samp{background}, and @samp{intensity} parameters with the
+appropriate types.
+
+Every style requires a @samp{foreground} and @samp{background}, but
+not every style needs an @samp{intensity}. If @var{add_intensity} is
+@code{True} (the default), then the @samp{intensity} parameter will be
+created. If @var{add_intensity} is @code{False}, then the
+@samp{intensity} parameter will not be created.
+
+If the @samp{intensity} parameter is not created, then the
+@code{gdb.Style} (@pxref{Styles In Python}) created from this
+@code{gdb.StyleParameterSet} will have @code{gdb.INTENSITY_NORMAL}.
+
+The @var{doc} should be a string which will be used as the help text
+for the @var{name} prefix command. This text is used as the
+@code{Command.__doc__} value for the @code{gdb.Command} object that is
+the prefix command object (@pxref{CLI Commands In Python}). If
+@var{doc} is @code{None} (the default) then a basic default help text
+is used.
+@end defun
+
+@defun StyleParameterSet.apply(string)
+Equivalent to @code{StyleParameterSet.style.apply(string)}. Returns a
+copy of @var{string} with escape sequences added to the start and end.
+The escape sequence at the start applies this style, and the escape
+sequence at the end restores the terminal default.
+
+If styling is disabled (i.e.@: @samp{set style enabled off}), then no
+escape sequences are added and this method returns a copy of
+@var{string}.
+@end defun
+
+@defvar StyleParameterSet.style
+This read/write attribute holds a @code{gdb.Style} object
+(@pxref{Styles In Python}), that is a named style associated with this
+style parameter group.
+@end defvar
+
+@defvar StyleParameterSet.value
+This is an alias for @code{StyleParameterSet.style}, see above.
+@end defvar
+
@node Functions In Python
@subsubsection Writing new convenience functions
@@ -7275,7 +7387,8 @@ Styles In Python
@value{GDBN} has many styles builtin (@pxref{Output Styling}), and
style objects can be created that apply these builtin styles to Python
-output.
+output. It is also possible to create new styles which can be used to
+style Python output (@pxref{Creating Style Parameters}).
The style class is called @code{gdb.Style}, and has the following
methods and attributes:
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index cedd897ab0f..f2f57280007 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -515,3 +515,212 @@ class ParameterPrefix:
def dont_repeat(self):
if self.active_prefix is not None:
self.active_prefix.dont_repeat()
+
+
+class StyleParameterSet:
+ """Create new style parameters.
+
+ A style parameter is a set of parameters that start with 'set style ...'
+ and 'show style ...'. For example 'filename' is a style parameter, and
+ 'disassembler symbol' is another style parameter.
+
+ The name of the style parameter is really a prefix command. Under this
+ we must have two commands 'foreground' and 'background', which are color
+ parameters. A third, optional command 'intensity', is an enum with
+ values 'normal', 'bold', and 'dim'.
+
+ A StyleParameterSet is initialised with a name, e.g. 'filename' or
+ 'disassembler symbol'. The StyleParameterSet creates the prefix
+ commands in the 'set style' and 'show style' name space, and then adds
+ the 'foreground', 'background', and optionally, the 'intensity'
+ commands.
+
+ If you want a whole new style group, similar to 'disassembler',
+ then you need to add this yourself first, then StyleParameterSet
+ can be used to create styles within the new prefix group.
+
+ The 'value' attribute on this object can be used to get and set a
+ gdb.Style object which controls all aspects of this style.
+
+ For readability, the alias 'style' is the same as 'value'.
+ """
+
+ def __init__(self, name, add_intensity=True, doc=None):
+ # The STYLE_NAME is something like 'filename' is 'set style
+ # filename ...', and PARAM_NAME is one of 'foreground',
+ # 'background', or 'intensity'. The DESC_TEXT is the long
+ # form used in help text, like 'foreground color' or 'display
+ # intensity'. The DEFAULT_VALUE is used to set the SELF.value
+ # attribute. And PARAM_TYPE is a gdb.PARAM_* constant. The
+ # ARGS is used for gdb.PARAM_ENUM, which ARGS should be the
+ # enum value list.
+ class style_parameter(Parameter):
+ def __init__(
+ self,
+ style_name,
+ parent_obj,
+ param_name,
+ desc_text,
+ default_value,
+ param_type,
+ *args
+ ):
+ # Setup documentation must be done before calling
+ # parent's __init__ method, as the __init__ reads (and
+ # copies) these values.
+ self.show_doc = "Show the " + desc_text + " for this property."
+ self.set_doc = "Set the " + desc_text + " for this property."
+ self.__doc__ = ""
+
+ # Call the parent's __init__ method to actually create
+ # the parameter.
+ super().__init__(
+ "style " + style_name + " " + param_name,
+ COMMAND_NONE,
+ param_type,
+ *args
+ )
+
+ # Store information we need in other methods.
+ self._style_name = style_name
+ self._desc_text = desc_text
+ self._parent = parent_obj
+
+ # Finally, setup the default value.
+ self.value = default_value
+
+ # Return the 'show style <style-name> <attribute>' string,
+ # which has styling applied.
+ def get_show_string(self, value):
+ s = self._parent.style
+ return (
+ "The "
+ + s.apply('"' + self._style_name + '" style')
+ + " "
+ + self._desc_text
+ + " is: "
+ + value
+ )
+
+ class style_foreground_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "foreground",
+ "foreground color",
+ Color(),
+ PARAM_COLOR,
+ )
+
+ class style_background_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "background",
+ "background color",
+ Color(),
+ PARAM_COLOR,
+ )
+
+ class style_intensity_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "intensity",
+ "display intensity",
+ "normal",
+ PARAM_ENUM,
+ ["normal", "bold", "dim"],
+ )
+
+ if doc is None:
+ doc = (
+ "The "
+ + name
+ + " display styling.\nConfigure "
+ + name
+ + " colors and display intensity."
+ )
+
+ ParameterPrefix("style " + name, COMMAND_NONE, doc)
+ self._foreground = style_foreground_parameter(name, self)
+ self._background = style_background_parameter(name, self)
+ if add_intensity:
+ self._intensity = style_intensity_parameter(name, self)
+ self._name = name
+ self._style = None
+
+ @property
+ def value(self):
+ """Return the gdb.Style object for this parameter set."""
+ if self._style is None:
+ self._style = Style(self._name)
+ return self._style
+
+ @property
+ def style(self):
+ """Return the gdb.Style object for this parameter set.
+
+ This is an alias for self.value."""
+ return self.value
+
+ @value.setter
+ def value(self, new_value):
+ """Set this parameter set to NEW_VALUE, a gdb.Style object.
+
+ The attributes of NEW_VALUE are used to update the current settings
+ of this parameter set. If this parameter set was created without
+ an intensity setting, then the intensity of NEW_VALUE is ignored."""
+ if not isinstance(new_value, Style):
+ raise TypeError("value must be gdb.Style, not %s" % type(new_value))
+ self._foreground.value = new_value.foreground
+ self._background.value = new_value.background
+ if hasattr(self, "_intensity"):
+ intensity_value = new_value.intensity
+ if intensity_value == INTENSITY_BOLD:
+ intensity_string = "bold"
+ elif intensity_value == INTENSITY_DIM:
+ intensity_string = "dim"
+ elif intensity_value == INTENSITY_NORMAL:
+ intensity_string = "normal"
+ else:
+ raise ValueError(
+ "unknown intensity value %d from Style" % intensity_value
+ )
+
+ self._intensity.value = intensity_string
+
+ @style.setter
+ def style(self, new_value):
+ """Set this parameter set to NEW_VALUE, a gdb.Style object.
+
+ This is an alias for self.value."""
+ self.value = new_value
+
+ def apply(self, *args, **kwargs):
+ """Apply this style to the arguments.
+
+ Forwards all arguments to self.style.apply(). The arguments should be a string,
+ to which this style is applied. This function returns the same string with
+ escape sequences added to apply this style.
+
+ If styling is globally disabled ('set style enabled off') then no escape sequences
+ will be addedded, the input string is returned."""
+ return self.style.apply(*args, **kwargs)
+
+ def __repr__(self):
+ """A string representation of SELF."""
+
+ def full_typename(obj):
+ module = type(obj).__module__
+ qualname = type(obj).__qualname__
+
+ if module is None or module == "builtins":
+ return qualname
+ else:
+ return module + "." + qualname
+
+ return "<" + full_typename(self) + " name='" + self._name + "'>"
diff --git a/gdb/testsuite/gdb.python/py-style-parameter-set.exp b/gdb/testsuite/gdb.python/py-style-parameter-set.exp
new file mode 100644
index 00000000000..73d94d1bb51
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-style-parameter-set.exp
@@ -0,0 +1,366 @@
+# 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.StyleParameterSet.
+
+load_lib gdb-python.exp
+
+require allow_python_tests
+
+# Create a regexp that can be used to match the output of a 'show style
+# NAME' command. HAS_INTENSITY is a boolean and indicates if style NAME has
+# an intensity attribute.
+proc gen_show_style_re { name has_intensity } {
+ set output \
+ [list \
+ "style ${name} background: The \"${name}\" style background color is: none" \
+ "style ${name} foreground: The \"${name}\" style foreground color is: none"]
+
+ if { $has_intensity } {
+ lappend output \
+ "style ${name} intensity: The \"${name}\" style display intensity is: normal"
+ }
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'set style NAME' command,
+# that is, a 'set' command that doesn't actually set an attribute of a
+# style, in this case GDB will print some useful help text. HAS_INTENSITY is
+# a boolean and indicates if style NAME has an intensity attribute or not.
+proc gen_set_style_re { name has_intensity } {
+ set output \
+ [list \
+ "List of \"set style $name\" subcommands:" \
+ "" \
+ "set style $name background -- Set the background color for this property\\." \
+ "set style $name foreground -- Set the foreground color for this property\\."]
+
+ if { $has_intensity } {
+ lappend output \
+ "set style $name intensity -- Set the display intensity for this property\\."
+ }
+
+ lappend output \
+ "" \
+ "Type \"help set style $name\" followed by subcommand name for full documentation\\." \
+ "Type \"apropos word\" to search for commands related to \"word\"\\." \
+ "Type \"apropos -v word\" for full documentation of commands related to \"word\"\\." \
+ "Command name abbreviations are allowed if unambiguous\\."
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'help show style NAME'
+# command. HAS_INTENSITY is a boolean and indicates if style NAME has an
+# intensity attribute or not. DOC is a regexp that matches the doc string
+# for style NAME.
+proc gen_help_show_style_re { name has_intensity doc } {
+ set output \
+ [list \
+ $doc \
+ "" \
+ "List of \"show style ${name}\" subcommands:" \
+ "" \
+ "show style $name background -- Show the background color for this property\\." \
+ "show style $name foreground -- Show the foreground color for this property\\."]
+ if { $has_intensity } {
+ lappend output \
+ "show style $name intensity -- Show the display intensity for this property\\."
+ }
+
+ lappend output \
+ "" \
+ "Type \"help show style $name\" followed by subcommand name for full documentation\\." \
+ "Type \"apropos word\" to search for commands related to \"word\"\\." \
+ "Type \"apropos -v word\" for full documentation of commands related to \"word\"\\." \
+ "Command name abbreviations are allowed if unambiguous\\."
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'help set style NAME'
+# command. HAS_INTENSITY is a boolean and indicates if style NAME has an
+# intensity attribute or not. DOC is a regexp that matches the doc string
+# for style NAME.
+proc gen_help_set_style_re { name has_intensity doc } {
+ return \
+ [multi_line \
+ $doc \
+ "" \
+ [gen_set_style_re $name $has_intensity]]
+}
+
+# Create styles with and without intensity. Use named and unnamed
+# argument passing, and vary the argument passing order. Create
+# styles with and without documentation.
+#
+# Confirm that the styles contain the expected sub-commands, and that
+# the documentation is as expected.
+proc_with_prefix test_basic_usage {} {
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"style-1\")" \
+ "create style-1"
+
+ gdb_test_no_output \
+ "python style_2 = gdb.StyleParameterSet(add_intensity=True, name=\"style-2\")" \
+ "create style-2"
+
+ gdb_test_no_output \
+ "python style_3 = gdb.StyleParameterSet(name=\"style-3\", doc=\"Style-3 display styling.\\nThis is a multi-line documentation\\nstring describing style-3.\")" \
+ "create style-3"
+
+ gdb_test_no_output \
+ "python style_4 = gdb.StyleParameterSet(\"style-4\", add_intensity=False)" \
+ "create style-4"
+
+ foreach style { style-1 style-2 style-3 } {
+ gdb_test "show style $style" \
+ [gen_show_style_re $style true]
+ gdb_test "set style $style" \
+ [gen_set_style_re $style true]
+ }
+
+ gdb_test "show style style-4" \
+ [gen_show_style_re "style-4" false]
+
+ foreach style { style-1 style-2 } {
+ gdb_test "help show style $style" \
+ [gen_help_show_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+
+
+ set out [gen_help_set_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+
+ gdb_test "help set style $style" \
+ [gen_help_set_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+ }
+
+ gdb_test "help show style style-3" \
+ [gen_help_show_style_re "style-3" true \
+ [multi_line \
+ "Style-3 display styling\\." \
+ "This is a multi-line documentation" \
+ "string describing style-3\\."]]
+
+ gdb_test "help show style style-4" \
+ [gen_help_show_style_re "style-4" false \
+ [multi_line \
+ "The style-4 display styling\\." \
+ "Configure style-4 colors and display intensity\\."]]
+
+ for { set i 1 } { $i < 5 } { incr i } {
+ gdb_test "python print(style_$i)" \
+ "<gdb.StyleParameterSet name='style-$i'>" \
+ "print repr of style_$i"
+ }
+
+ # There is no 'style-4 intensity' property.
+ gdb_test "show style style-4 foreground" \
+ "The \"style-4\" style foreground color is: none"
+ gdb_test "show style style-4 background" \
+ "The \"style-4\" style background color is: none"
+ gdb_test "show style style-4 intensity" \
+ "Undefined show style style-4 command: \"intensity\"\\. Try \"help show style style-4\"\\."
+
+ # There is a 'style-1 intensity' property.
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: none" \
+ "show style-1 foreground before changes"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: none" \
+ "show style-1 background before changes"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: normal" \
+ "show style-1 intensity before changes"
+
+ # Grab the gdb.Style objects from 'style-1'.
+ gdb_test_no_output "python s1 = style_1.style"
+ gdb_test_no_output "python s2 = style_1.value"
+
+ # Check both represent the same style.
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=none, bg=none, intensity=normal>" \
+ "print s1 style before changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=none, bg=none, intensity=normal>" \
+ "print s2 style before changes"
+
+ gdb_test_no_output \
+ "python s1.foreground=gdb.Color('red')" "set foreground"
+ gdb_test_no_output \
+ "python s1.background=gdb.Color('blue')" "set background"
+ gdb_test_no_output \
+ "python s1.intensity=gdb.INTENSITY_DIM" "set intensity"
+
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=red, bg=blue, intensity=dim>" \
+ "print s1 style after first set of changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=red, bg=blue, intensity=dim>" \
+ "print s2 style after first set of changes"
+
+ # Check the style properties have updated.
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: red" \
+ "show style-1 foreground after first set of changes"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: blue" \
+ "show style-1 background after first set of changes"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: dim" \
+ "show style-1 intensity after first set of changes"
+
+ # Change the style properties, check gdb.Style objects update.
+ gdb_test_no_output "set style style-1 foreground yellow"
+ gdb_test_no_output "set style style-1 background cyan"
+ gdb_test_no_output "set style style-1 intensity bold"
+
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=yellow, bg=cyan, intensity=bold>" \
+ "print s1 style after second set of changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=yellow, bg=cyan, intensity=bold>" \
+ "print s2 style after second set of changes"
+
+ # Assign a gdb.Style to set 'style-1'. First create some unnamed
+ # style objects that can be used.
+ gdb_test_no_output \
+ "python uns1 = gdb.Style(foreground=gdb.Color('white'), background=gdb.Color('black'), intensity=gdb.INTENSITY_BOLD)" \
+ "create uns1"
+ gdb_test_no_output \
+ "python uns2 = gdb.Style(foreground=gdb.Color('black'), background=gdb.Color('white'), intensity=gdb.INTENSITY_DIM)" \
+ "create uns2"
+
+ gdb_test_no_output "python style_1.value = uns1"
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: white" \
+ "show style-1 foreground after assigning uns1"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: black" \
+ "show style-1 background after assigning uns1"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: bold" \
+ "show style-1 intensity after assigning uns1"
+
+ gdb_test_no_output "python style_1.style = uns2"
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: black" \
+ "show style-1 foreground after assigning uns2"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: white" \
+ "show style-1 background after assigning uns2"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: dim" \
+ "show style-1 intensity after assigning uns2"
+
+ # Assign a style with an intensity that is not 'NORMAL' to a
+ # StyleParameterSet that doesn't have an intensity. The new
+ # intensity setting should be ignored.
+ gdb_test_no_output "python style_4.style = uns1"
+ gdb_test "show style style-4 foreground" \
+ "The \"style-4\" style foreground color is: white" \
+ "show style-4 foreground after assigning uns1"
+ gdb_test "show style style-4 background" \
+ "The \"style-4\" style background color is: black" \
+ "show style-4 background after assigning uns1"
+ gdb_test "show style style-4 intensity" \
+ "Undefined show style style-4 command: \"intensity\"\\. Try \"help show style style-4\"\\." \
+ "show style-4 intensity after assigning uns1"
+
+ gdb_test "python print(style_4.style)" \
+ "<gdb.Style name='style-4', fg=white, bg=black, intensity=normal>" \
+ "print string repr of style_4's style"
+}
+
+# Test creating a style prefix with gdb.ParameterPrefix, then adding
+# some styles within the new prefix. Change the style through the CLI
+# and confirm that the associated Python object updated as expected.
+proc_with_prefix test_style_prefix {} {
+ gdb_test_no_output \
+ "python gdb.ParameterPrefix(\"style my-style-group\", gdb.COMMAND_NONE)"
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"my-style-group style-1\")" \
+ "create style-1"
+ gdb_test_no_output \
+ "python style_2 = gdb.StyleParameterSet(\"my-style-group style-2\")" \
+ "create style-2"
+
+ gdb_test "python print(style_1.style)" \
+ "<gdb.Style name='my-style-group style-1', fg=none, bg=none, intensity=normal>" \
+ "print 'my-style-group style-1' style before changes"
+ gdb_test "python print(style_2.style)" \
+ "<gdb.Style name='my-style-group style-2', fg=none, bg=none, intensity=normal>" \
+ "print 'my-style-group style-2' style before changes"
+
+ gdb_test_no_output "set style my-style-group style-1 foreground red"
+ gdb_test_no_output "set style my-style-group style-1 background yellow"
+ gdb_test_no_output "set style my-style-group style-1 intensity bold"
+ gdb_test_no_output "set style my-style-group style-2 foreground black"
+ gdb_test_no_output "set style my-style-group style-2 background blue"
+ gdb_test_no_output "set style my-style-group style-2 intensity dim"
+
+ gdb_test "python print(style_1.style)" \
+ "<gdb.Style name='my-style-group style-1', fg=red, bg=yellow, intensity=bold>" \
+ "print 'my-style-group style-1' style after changes"
+ gdb_test "python print(style_2.style)" \
+ "<gdb.Style name='my-style-group style-2', fg=black, bg=blue, intensity=dim>" \
+ "print 'my-style-group style-2' style after changes"
+}
+
+# Test that gdb.StyleParameterSet.apply() works as expected.
+proc_with_prefix test_applying {} {
+ # Create a new StyleParameterSet, and adjust its settings.
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"style-1\")" \
+ "create style-1"
+ gdb_test_no_output \
+ "python uns1 = gdb.Style(foreground=gdb.Color('red'), background=gdb.Color('blue'), intensity=gdb.INTENSITY_BOLD)" \
+ "create uns1"
+ gdb_test_no_output "python style_1 = uns1"
+
+ # When styling is off (which it currently is), no escape sequences
+ # should be added.
+ gdb_test \
+ "python print(style_1.apply('xxx'))" "^xxx" \
+ "apply StyleParameterSet to a string when styling is off"
+
+ # When styling is on, we should see an escape sequence added.
+ gdb_test "with style enabled on -- python print(style_1.apply('xxx'))" \
+ "\033\\\[31;44;1;23;24;27mxxx\033\\\[m" \
+ "apply a style when styling is on"
+}
+
+# Start GDB.
+with_ansi_styling_terminal {
+ clean_restart
+}
+
+# Turn styling off so that the output of 'show style ...' isn't styled, this
+# makes it easier to match the output.
+gdb_test_no_output "set style enabled off"
+
+# Run the tests.
+test_basic_usage
+test_style_prefix
+test_applying
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* Re: [PATCHv7 4/4] gdb/python: extend gdb.write to support styled output
2025-08-13 10:30 ` [PATCHv7 4/4] gdb/python: extend gdb.write to support styled output Andrew Burgess
@ 2025-08-13 12:37 ` Eli Zaretskii
0 siblings, 0 replies; 61+ messages in thread
From: Eli Zaretskii @ 2025-08-13 12:37 UTC (permalink / raw)
To: Andrew Burgess; +Cc: gdb-patches
> From: Andrew Burgess <aburgess@redhat.com>
> Cc: Andrew Burgess <aburgess@redhat.com>
> Date: Wed, 13 Aug 2025 11:30:52 +0100
>
> It is already possible to produce styled output from Python by
> converting the gdb.Style to its escape code sequence, and writing that
> to the output stream.
>
> But this commit adds an alternative option to the mix by extending the
> existing gdb.write() function to accept a 'style' argument. The value
> of this argument can be 'None' to indicate no style change should be
> performed, this is the default, and matches the existing behaviour.
>
> Or the new 'style' argument can be a gdb.Style object, in which case
> the specified style is applied only for the string passed to
> gdb.write, after which the default style is re-applied.
>
> Using gdb.write with a style object more closely matches how GDB
> handles styling internally, and has the benefit that the user doesn't
> need to remember to restore the default style when they are done.
> ---
> gdb/NEWS | 3 ++
> gdb/doc/python.texi | 9 +++-
> gdb/python/py-style.c | 27 ++++++++++
> gdb/python/python-internal.h | 15 ++++++
> gdb/python/python.c | 30 ++++++++++--
> .../gdb.python/py-color-pagination.exp | 49 +++++++++++++++++++
> .../gdb.python/py-color-pagination.py | 16 ++++++
> gdb/testsuite/gdb.python/py-style.exp | 29 +++++++++++
> 8 files changed, 172 insertions(+), 6 deletions(-)
The documentation parts are okay, thanks.
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
^ permalink raw reply [flat|nested] 61+ messages in thread
* Re: [PATCHv7 3/4] gdb/python: new class gdb.StyleParameterSet
2025-08-13 10:34 ` [PATCHv7 3/4] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
@ 2025-08-13 13:05 ` Eli Zaretskii
2025-08-13 13:05 ` Eli Zaretskii
1 sibling, 0 replies; 61+ messages in thread
From: Eli Zaretskii @ 2025-08-13 13:05 UTC (permalink / raw)
To: Andrew Burgess; +Cc: gdb-patches
> From: Andrew Burgess <aburgess@redhat.com>
> Cc: Andrew Burgess <aburgess@redhat.com>
> Date: Wed, 13 Aug 2025 11:34:30 +0100
>
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -144,6 +144,10 @@ info threads [-gid] [-stopped] [-running] [ID]...
> gdb.INTENSITY_DIM for use with gdb.Style when representing
> intensities.
>
> + ** New gdb.StyleParameterSet for creating custom style settings.
> + Use gdb.StyleParameterSet(NAME) to create 'set style NAME ...'
> + and 'show style NAME ...' parameters.
> +
This part is okay.
> +@smallexample
> +@group
> +(@value{GDBP}) python s = gdb.StyleParameterSet("my-style")
> +(@value{GDBP}) show style my-style
> +style my-style background: The "my-style" style background color is: none
> +style my-style foreground: The "my-style" style foreground color is: none
> +style my-style intensity: The "my-style" style display intensity is: normal
> +(@value{GDBP})
> +@end group
> +@end smallexample
> +
> +You might also want to group a number of styles within a new prefix,
> +similar to how @value{GDBN} groups disassembler related styles within
> +the @samp{style disassembler} prefix. This can be done using
> +@code{gdb.ParameterPrefix} (@pxref{gdb.ParameterPrefix}), as in this
> +example:
> +
> +@smallexample
> +@group
> +(@value{GDBP}) python gdb.ParameterPrefix("style my-group", gdb.COMMAND_NONE)
> +(@value{GDBP}) python s_a = gdb.StyleParameterSet("my-group aaa")
> +(@value{GDBP}) python s_b = gdb.StyleParameterSet("my-group bbb")
> +(@value{GDBP}) show style my-group
> +style my-group aaa background: The "my-group aaa" style background color is: none
> +style my-group aaa foreground: The "my-group aaa" style foreground color is: none
> +style my-group aaa intensity: The "my-group aaa" style display intensity is: normal
> +style my-group bbb background: The "my-group bbb" style background color is: none
> +style my-group bbb foreground: The "my-group bbb" style foreground color is: none
> +style my-group bbb intensity: The "my-group bbb" style display intensity is: normal
These line are too long. Could you make the strings shorter, so that
the lines became shorter?
Okay, with the above fixed, thanks.
^ permalink raw reply [flat|nested] 61+ messages in thread
* Re: [PATCHv7 3/4] gdb/python: new class gdb.StyleParameterSet
2025-08-13 10:34 ` [PATCHv7 3/4] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
2025-08-13 13:05 ` Eli Zaretskii
@ 2025-08-13 13:05 ` Eli Zaretskii
1 sibling, 0 replies; 61+ messages in thread
From: Eli Zaretskii @ 2025-08-13 13:05 UTC (permalink / raw)
To: Andrew Burgess; +Cc: gdb-patches
> From: Andrew Burgess <aburgess@redhat.com>
> Cc: Andrew Burgess <aburgess@redhat.com>
> Date: Wed, 13 Aug 2025 11:34:30 +0100
>
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -144,6 +144,10 @@ info threads [-gid] [-stopped] [-running] [ID]...
> gdb.INTENSITY_DIM for use with gdb.Style when representing
> intensities.
>
> + ** New gdb.StyleParameterSet for creating custom style settings.
> + Use gdb.StyleParameterSet(NAME) to create 'set style NAME ...'
> + and 'show style NAME ...' parameters.
> +
This part is okay.
> +@smallexample
> +@group
> +(@value{GDBP}) python s = gdb.StyleParameterSet("my-style")
> +(@value{GDBP}) show style my-style
> +style my-style background: The "my-style" style background color is: none
> +style my-style foreground: The "my-style" style foreground color is: none
> +style my-style intensity: The "my-style" style display intensity is: normal
> +(@value{GDBP})
> +@end group
> +@end smallexample
> +
> +You might also want to group a number of styles within a new prefix,
> +similar to how @value{GDBN} groups disassembler related styles within
> +the @samp{style disassembler} prefix. This can be done using
> +@code{gdb.ParameterPrefix} (@pxref{gdb.ParameterPrefix}), as in this
> +example:
> +
> +@smallexample
> +@group
> +(@value{GDBP}) python gdb.ParameterPrefix("style my-group", gdb.COMMAND_NONE)
> +(@value{GDBP}) python s_a = gdb.StyleParameterSet("my-group aaa")
> +(@value{GDBP}) python s_b = gdb.StyleParameterSet("my-group bbb")
> +(@value{GDBP}) show style my-group
> +style my-group aaa background: The "my-group aaa" style background color is: none
> +style my-group aaa foreground: The "my-group aaa" style foreground color is: none
> +style my-group aaa intensity: The "my-group aaa" style display intensity is: normal
> +style my-group bbb background: The "my-group bbb" style background color is: none
> +style my-group bbb foreground: The "my-group bbb" style foreground color is: none
> +style my-group bbb intensity: The "my-group bbb" style display intensity is: normal
These line are too long. Could you make the strings shorter, so that
the lines became shorter?
Okay, with the above fixed, thanks.
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
^ permalink raw reply [flat|nested] 61+ messages in thread
* Re: [PATCHv7 1/4] gdb: allow gdb.Color to work correctly with pagination
2025-08-13 10:25 ` [PATCHv7 1/4] gdb: allow gdb.Color to work correctly with pagination Andrew Burgess
@ 2025-08-24 16:13 ` Andrew Burgess
2025-08-25 10:21 ` Tom de Vries
0 siblings, 1 reply; 61+ messages in thread
From: Andrew Burgess @ 2025-08-24 16:13 UTC (permalink / raw)
To: gdb-patches
Andrew Burgess <aburgess@redhat.com> writes:
> This commit allows gdb.Color objects to be used to style output from
> GDB commands written in Python, and the styled output should work
> correctly with pagination.
I've gone ahead and pushed this fix.
I believe that the change I've made here is inline with how the same
problem is handled in the TUI, parsing the escape sequence and then
using the resulting ui_file_style object. And the second part of the
fix, changing gdb_printf to gdb_puts falls out from making the first
change.
Given that the gdb.Color API was added since GDB 16, it seems sensible
to get this bug fixed before GDB 17 branches.
The other patches in this series I've not pushed, pending review.
Thanks,
Andrew
>
> There are two parts to fixing this:
>
> First, GDB needs to be able to track the currently applied style
> within the page_file class. This means that style changes need to be
> achieved with calls to pager_file::emit_style_escape.
>
> Now usually, GDB does this by calling something like fprintf_styled,
> which takes care to apply the style for us. However, that's not
> really an option here as a gdb.Color isn't a full style, and as the
> gdb.Color object is designed to be converted directly into escape
> sequences that can then be printed, we really need a solution that
> works with this approach.
>
> However pager_file::puts already has code in place to handle escape
> sequences. Right now all this code does is spot the escape sequence
> and append it to the m_wrap_buffer. But in this commit I propose that
> we go one step further, parse the escape sequence back into a
> ui_file_style object in pager_file::puts, and then we can call
> pager_file::emit_style_escape.
>
> If the parsing doesn't work then we can just add the escape sequence
> to m_wrap_buffer as we did before.
>
> But wait, how can this work if a gdb.Color isn't a full style? Turns
> out that's not a problem. We only ever emit the escape sequence for
> those parts of a style that need changing, so a full style that sets
> the foreground color will emit the same escape sequence as a gdb.Color
> for the foreground. When we convert the escape sequence back into a
> ui_file_style, then we get a style with everything set to default,
> except the foreground color.
>
> I had hoped that this would be all that was needed. But unfortunately
> this doesn't work because of the second problem...
>
> ... the implementation of the Python function gdb.write() calls
> gdb_printf(), which calls gdb_vprintf(), which calls ui_file::vprintf,
> which calls ui_out::vmessage, which calls ui_out::call_do_message, and
> finally we reach cli_ui_out::do_message. This final do_message
> function does this:
>
> ui_file *stream = m_streams.back ();
> stream->emit_style_escape (style);
> stream->puts (str.c_str ());
> stream->emit_style_escape (ui_file_style ());
>
> If we imagine the case where we are emitting a style, triggered from
> Python like this:
>
> gdb.write(gdb.Color('red').escape_sequence(True))
>
> the STYLE in this case will be the default ui_file_style(), and STR
> will hold the escape sequence we are writing.
>
> After the first change, where pager_file::puts now calls
> pager_file::emit_style_escape, the current style of STREAM will have
> been updated. But this means that the final emit_style_escape will
> now restore the default style.
>
> The fix for this is to avoid using the high level gdb_printf from
> gdb.write(), and instead use gdb_puts instead. The gdb_puts function
> doesn't restore the default style, which means our style modification
> survives.
>
> There's a new test included. This test includes what appears like a
> pointless extra loop (looping over a single value), but later commits
> in this series will add more values to this list.
> ---
> gdb/python/python.c | 18 +--
> .../gdb.python/py-color-pagination.exp | 122 ++++++++++++++++++
> .../gdb.python/py-color-pagination.py | 46 +++++++
> gdb/utils.c | 21 ++-
> 4 files changed, 195 insertions(+), 12 deletions(-)
> create mode 100644 gdb/testsuite/gdb.python/py-color-pagination.exp
> create mode 100644 gdb/testsuite/gdb.python/py-color-pagination.py
>
> diff --git a/gdb/python/python.c b/gdb/python/python.c
> index cb0d642a67d..1af7896eb08 100644
> --- a/gdb/python/python.c
> +++ b/gdb/python/python.c
> @@ -1570,21 +1570,21 @@ gdbpy_write (PyObject *self, PyObject *args, PyObject *kw)
>
> try
> {
> + ui_file *stream;
> switch (stream_type)
> {
> case 1:
> - {
> - gdb_printf (gdb_stderr, "%s", arg);
> - break;
> - }
> + stream = gdb_stderr;
> + break;
> case 2:
> - {
> - gdb_printf (gdb_stdlog, "%s", arg);
> - break;
> - }
> + stream = gdb_stdlog;
> + break;
> default:
> - gdb_printf (gdb_stdout, "%s", arg);
> + stream = gdb_stdout;
> + break;
> }
> +
> + gdb_puts (arg, stream);
> }
> catch (const gdb_exception &except)
> {
> diff --git a/gdb/testsuite/gdb.python/py-color-pagination.exp b/gdb/testsuite/gdb.python/py-color-pagination.exp
> new file mode 100644
> index 00000000000..3235fffe5cc
> --- /dev/null
> +++ b/gdb/testsuite/gdb.python/py-color-pagination.exp
> @@ -0,0 +1,122 @@
> +# 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.Color and how this
> +# interacts with GDB's pagination system.
> +
> +load_lib gdb-python.exp
> +
> +require allow_python_tests
> +
> +standard_testfile
> +
> +set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
> +
> +set str "<[string repeat - 78]>"
> +
> +# These define all the default attributes for a style: background
> +# color, intensity, italics, and underlined.
> +set other_attr ";49;22;23;24;27"
> +
> +# These colors set the foreground color only. Everything else is the
> +# default.
> +set black "(?:\033\\\[30${other_attr}m)"
> +set red "(?:\033\\\[31${other_attr}m)"
> +set green "(?:\033\\\[32${other_attr}m)"
> +set yellow "(?:\033\\\[33${other_attr}m)"
> +set blue "(?:\033\\\[34${other_attr}m)"
> +set magenta "(?:\033\\\[35${other_attr}m)"
> +set cyan "(?:\033\\\[36${other_attr}m)"
> +set white "(?:\033\\\[37${other_attr}m)"
> +
> +set any_color "(?:${black}|${red}|${green}|${yellow}|${blue}|${magenta}|${cyan}|${white})"
> +
> +# Run the command 'TYPE-fill MODE' which fills the screen with output and
> +# triggers the pagination prompt. Check that styling is applied correctly
> +# to the output.
> +proc test_pagination { type mode } {
> +
> + # Start with a fresh GDB, but enable color support.
> + with_ansi_styling_terminal {
> + clean_restart
> + }
> +
> + gdb_test_no_output "source $::pyfile" "source the script"
> +
> + gdb_test_no_output "set width 80"
> + gdb_test_no_output "set height 15"
> +
> + set saw_bad_color_handling false
> + set expected_restore_color ""
> + set last_color ""
> + gdb_test_multiple "$type-fill $mode" "" {
> + -re "^$type-fill $mode\r\n" {
> + exp_continue
> + }
> +
> + -re "^(${::any_color}?)(${::any_color})$::str" {
> + # After a continuation prompt GDB will restore the previous
> + # color, and then we immediately switch to a new color.
> + set restored_color $expect_out(1,string)
> + if { $restored_color ne ""
> + && $restored_color ne $expected_restore_color } {
> + set saw_bad_color_handling true
> + }
> + set last_color $expect_out(2,string)
> + exp_continue
> + }
> +
> + -re "^\033\\\[${::decimal}m$::str" {
> + # This catches the case where the color's escape sequence has
> + # not been converted back into a full style. This indicates
> + # something went wrong in the pager_file::puts function.
> + set saw_bad_color_handling true
> + exp_continue
> + }
> +
> + -re "^((?:\033\\\[m)?)$::pagination_prompt$" {
> + # After a pagination prompt we expect GDB to restore the last
> + # color.
> + set expected_restore_color $last_color
> +
> + # If we didn't see a color reset sequence then the pagination
> + # prompt will have been printed in the wrong color, this is a
> + # GDB bug.
> + set color_reset $expect_out(1,string)
> + if { $color_reset eq "" } {
> + set saw_bad_color_handling true
> + }
> +
> + # Send '\n' to view more output.
> + send_gdb "\n"
> + exp_continue
> + }
> +
> + -re "^\r\n" {
> + # The matches the newline sent to the continuation prompt.
> + exp_continue
> + }
> +
> + -re "^\033\\\[m\r\n$::gdb_prompt $" {
> + gdb_assert { !$saw_bad_color_handling } $gdb_test_name
> + }
> + }
> +}
> +
> +foreach_with_prefix type { color } {
> + foreach_with_prefix mode { write print } {
> + test_pagination $type $mode
> + }
> +}
> diff --git a/gdb/testsuite/gdb.python/py-color-pagination.py b/gdb/testsuite/gdb.python/py-color-pagination.py
> new file mode 100644
> index 00000000000..efd501eedf5
> --- /dev/null
> +++ b/gdb/testsuite/gdb.python/py-color-pagination.py
> @@ -0,0 +1,46 @@
> +# 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/>.
> +
> +import gdb
> +
> +basic_colors = ["black", "red", "green", "yellow", "blue", "magenta", "cyan", "white"]
> +
> +
> +def write(mode, text):
> + if mode == "write":
> + gdb.write(text)
> + else:
> + print(text, end="")
> +
> +
> +class ColorTester(gdb.Command):
> + def __init__(self):
> + super().__init__("color-fill", gdb.COMMAND_USER)
> +
> + def invoke(self, args, from_tty):
> + mode = args
> + str = "<" + "-" * 78 + ">"
> + for i in range(0, 20):
> + for color_name in basic_colors:
> + c = gdb.Color(color_name)
> + write(mode, c.escape_sequence(True))
> + write(mode, str)
> +
> + default = gdb.Color("none")
> + write(mode, default.escape_sequence(True))
> + write(mode, "\n")
> +
> +
> +ColorTester()
> diff --git a/gdb/utils.c b/gdb/utils.c
> index 10d3d51e481..92e626a9c75 100644
> --- a/gdb/utils.c
> +++ b/gdb/utils.c
> @@ -1702,10 +1702,25 @@ pager_file::puts (const char *linebuffer)
> else if (*linebuffer == '\033'
> && skip_ansi_escape (linebuffer, &skip_bytes))
> {
> - m_wrap_buffer.append (linebuffer, skip_bytes);
> - /* Note that we don't consider this a character, so we
> + /* We don't consider escape sequences as characters, so we
> don't increment chars_printed here. */
> - linebuffer += skip_bytes;
> +
> + size_t style_len;
> + ui_file_style style;
> + if (style.parse (linebuffer, &style_len)
> + && style_len <= skip_bytes)
> + {
> + this->emit_style_escape (style);
> +
> + linebuffer += style_len;
> + skip_bytes -= style_len;
> + }
> +
> + if (skip_bytes > 0)
> + {
> + m_wrap_buffer.append (linebuffer, skip_bytes);
> + linebuffer += skip_bytes;
> + }
> }
> else if (*linebuffer == '\r')
> {
> --
> 2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* Re: [PATCHv7 1/4] gdb: allow gdb.Color to work correctly with pagination
2025-08-24 16:13 ` Andrew Burgess
@ 2025-08-25 10:21 ` Tom de Vries
2025-08-26 14:35 ` [PATCH] gdb/testsuite: work around empty substring bug in expect Andrew Burgess
0 siblings, 1 reply; 61+ messages in thread
From: Tom de Vries @ 2025-08-25 10:21 UTC (permalink / raw)
To: Andrew Burgess, gdb-patches
On 8/24/25 18:13, Andrew Burgess wrote:
> Andrew Burgess <aburgess@redhat.com> writes:
>
>> This commit allows gdb.Color objects to be used to style output from
>> GDB commands written in Python, and the styled output should work
>> correctly with pagination.
>
> I've gone ahead and pushed this fix.
>
The new test-case fails for me on aarch64-linux. I've filed a PR (
https://sourceware.org/bugzilla/show_bug.cgi?id=33321 ) to track this.
Thanks,
- Tom
> I believe that the change I've made here is inline with how the same
> problem is handled in the TUI, parsing the escape sequence and then
> using the resulting ui_file_style object. And the second part of the
> fix, changing gdb_printf to gdb_puts falls out from making the first
> change.
>
> Given that the gdb.Color API was added since GDB 16, it seems sensible
> to get this bug fixed before GDB 17 branches.
>
> The other patches in this series I've not pushed, pending review.
>
> Thanks,
> Andrew
>
>
>
>>
>> There are two parts to fixing this:
>>
>> First, GDB needs to be able to track the currently applied style
>> within the page_file class. This means that style changes need to be
>> achieved with calls to pager_file::emit_style_escape.
>>
>> Now usually, GDB does this by calling something like fprintf_styled,
>> which takes care to apply the style for us. However, that's not
>> really an option here as a gdb.Color isn't a full style, and as the
>> gdb.Color object is designed to be converted directly into escape
>> sequences that can then be printed, we really need a solution that
>> works with this approach.
>>
>> However pager_file::puts already has code in place to handle escape
>> sequences. Right now all this code does is spot the escape sequence
>> and append it to the m_wrap_buffer. But in this commit I propose that
>> we go one step further, parse the escape sequence back into a
>> ui_file_style object in pager_file::puts, and then we can call
>> pager_file::emit_style_escape.
>>
>> If the parsing doesn't work then we can just add the escape sequence
>> to m_wrap_buffer as we did before.
>>
>> But wait, how can this work if a gdb.Color isn't a full style? Turns
>> out that's not a problem. We only ever emit the escape sequence for
>> those parts of a style that need changing, so a full style that sets
>> the foreground color will emit the same escape sequence as a gdb.Color
>> for the foreground. When we convert the escape sequence back into a
>> ui_file_style, then we get a style with everything set to default,
>> except the foreground color.
>>
>> I had hoped that this would be all that was needed. But unfortunately
>> this doesn't work because of the second problem...
>>
>> ... the implementation of the Python function gdb.write() calls
>> gdb_printf(), which calls gdb_vprintf(), which calls ui_file::vprintf,
>> which calls ui_out::vmessage, which calls ui_out::call_do_message, and
>> finally we reach cli_ui_out::do_message. This final do_message
>> function does this:
>>
>> ui_file *stream = m_streams.back ();
>> stream->emit_style_escape (style);
>> stream->puts (str.c_str ());
>> stream->emit_style_escape (ui_file_style ());
>>
>> If we imagine the case where we are emitting a style, triggered from
>> Python like this:
>>
>> gdb.write(gdb.Color('red').escape_sequence(True))
>>
>> the STYLE in this case will be the default ui_file_style(), and STR
>> will hold the escape sequence we are writing.
>>
>> After the first change, where pager_file::puts now calls
>> pager_file::emit_style_escape, the current style of STREAM will have
>> been updated. But this means that the final emit_style_escape will
>> now restore the default style.
>>
>> The fix for this is to avoid using the high level gdb_printf from
>> gdb.write(), and instead use gdb_puts instead. The gdb_puts function
>> doesn't restore the default style, which means our style modification
>> survives.
>>
>> There's a new test included. This test includes what appears like a
>> pointless extra loop (looping over a single value), but later commits
>> in this series will add more values to this list.
>> ---
>> gdb/python/python.c | 18 +--
>> .../gdb.python/py-color-pagination.exp | 122 ++++++++++++++++++
>> .../gdb.python/py-color-pagination.py | 46 +++++++
>> gdb/utils.c | 21 ++-
>> 4 files changed, 195 insertions(+), 12 deletions(-)
>> create mode 100644 gdb/testsuite/gdb.python/py-color-pagination.exp
>> create mode 100644 gdb/testsuite/gdb.python/py-color-pagination.py
>>
>> diff --git a/gdb/python/python.c b/gdb/python/python.c
>> index cb0d642a67d..1af7896eb08 100644
>> --- a/gdb/python/python.c
>> +++ b/gdb/python/python.c
>> @@ -1570,21 +1570,21 @@ gdbpy_write (PyObject *self, PyObject *args, PyObject *kw)
>>
>> try
>> {
>> + ui_file *stream;
>> switch (stream_type)
>> {
>> case 1:
>> - {
>> - gdb_printf (gdb_stderr, "%s", arg);
>> - break;
>> - }
>> + stream = gdb_stderr;
>> + break;
>> case 2:
>> - {
>> - gdb_printf (gdb_stdlog, "%s", arg);
>> - break;
>> - }
>> + stream = gdb_stdlog;
>> + break;
>> default:
>> - gdb_printf (gdb_stdout, "%s", arg);
>> + stream = gdb_stdout;
>> + break;
>> }
>> +
>> + gdb_puts (arg, stream);
>> }
>> catch (const gdb_exception &except)
>> {
>> diff --git a/gdb/testsuite/gdb.python/py-color-pagination.exp b/gdb/testsuite/gdb.python/py-color-pagination.exp
>> new file mode 100644
>> index 00000000000..3235fffe5cc
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.python/py-color-pagination.exp
>> @@ -0,0 +1,122 @@
>> +# 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.Color and how this
>> +# interacts with GDB's pagination system.
>> +
>> +load_lib gdb-python.exp
>> +
>> +require allow_python_tests
>> +
>> +standard_testfile
>> +
>> +set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
>> +
>> +set str "<[string repeat - 78]>"
>> +
>> +# These define all the default attributes for a style: background
>> +# color, intensity, italics, and underlined.
>> +set other_attr ";49;22;23;24;27"
>> +
>> +# These colors set the foreground color only. Everything else is the
>> +# default.
>> +set black "(?:\033\\\[30${other_attr}m)"
>> +set red "(?:\033\\\[31${other_attr}m)"
>> +set green "(?:\033\\\[32${other_attr}m)"
>> +set yellow "(?:\033\\\[33${other_attr}m)"
>> +set blue "(?:\033\\\[34${other_attr}m)"
>> +set magenta "(?:\033\\\[35${other_attr}m)"
>> +set cyan "(?:\033\\\[36${other_attr}m)"
>> +set white "(?:\033\\\[37${other_attr}m)"
>> +
>> +set any_color "(?:${black}|${red}|${green}|${yellow}|${blue}|${magenta}|${cyan}|${white})"
>> +
>> +# Run the command 'TYPE-fill MODE' which fills the screen with output and
>> +# triggers the pagination prompt. Check that styling is applied correctly
>> +# to the output.
>> +proc test_pagination { type mode } {
>> +
>> + # Start with a fresh GDB, but enable color support.
>> + with_ansi_styling_terminal {
>> + clean_restart
>> + }
>> +
>> + gdb_test_no_output "source $::pyfile" "source the script"
>> +
>> + gdb_test_no_output "set width 80"
>> + gdb_test_no_output "set height 15"
>> +
>> + set saw_bad_color_handling false
>> + set expected_restore_color ""
>> + set last_color ""
>> + gdb_test_multiple "$type-fill $mode" "" {
>> + -re "^$type-fill $mode\r\n" {
>> + exp_continue
>> + }
>> +
>> + -re "^(${::any_color}?)(${::any_color})$::str" {
>> + # After a continuation prompt GDB will restore the previous
>> + # color, and then we immediately switch to a new color.
>> + set restored_color $expect_out(1,string)
>> + if { $restored_color ne ""
>> + && $restored_color ne $expected_restore_color } {
>> + set saw_bad_color_handling true
>> + }
>> + set last_color $expect_out(2,string)
>> + exp_continue
>> + }
>> +
>> + -re "^\033\\\[${::decimal}m$::str" {
>> + # This catches the case where the color's escape sequence has
>> + # not been converted back into a full style. This indicates
>> + # something went wrong in the pager_file::puts function.
>> + set saw_bad_color_handling true
>> + exp_continue
>> + }
>> +
>> + -re "^((?:\033\\\[m)?)$::pagination_prompt$" {
>> + # After a pagination prompt we expect GDB to restore the last
>> + # color.
>> + set expected_restore_color $last_color
>> +
>> + # If we didn't see a color reset sequence then the pagination
>> + # prompt will have been printed in the wrong color, this is a
>> + # GDB bug.
>> + set color_reset $expect_out(1,string)
>> + if { $color_reset eq "" } {
>> + set saw_bad_color_handling true
>> + }
>> +
>> + # Send '\n' to view more output.
>> + send_gdb "\n"
>> + exp_continue
>> + }
>> +
>> + -re "^\r\n" {
>> + # The matches the newline sent to the continuation prompt.
>> + exp_continue
>> + }
>> +
>> + -re "^\033\\\[m\r\n$::gdb_prompt $" {
>> + gdb_assert { !$saw_bad_color_handling } $gdb_test_name
>> + }
>> + }
>> +}
>> +
>> +foreach_with_prefix type { color } {
>> + foreach_with_prefix mode { write print } {
>> + test_pagination $type $mode
>> + }
>> +}
>> diff --git a/gdb/testsuite/gdb.python/py-color-pagination.py b/gdb/testsuite/gdb.python/py-color-pagination.py
>> new file mode 100644
>> index 00000000000..efd501eedf5
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.python/py-color-pagination.py
>> @@ -0,0 +1,46 @@
>> +# 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/>.
>> +
>> +import gdb
>> +
>> +basic_colors = ["black", "red", "green", "yellow", "blue", "magenta", "cyan", "white"]
>> +
>> +
>> +def write(mode, text):
>> + if mode == "write":
>> + gdb.write(text)
>> + else:
>> + print(text, end="")
>> +
>> +
>> +class ColorTester(gdb.Command):
>> + def __init__(self):
>> + super().__init__("color-fill", gdb.COMMAND_USER)
>> +
>> + def invoke(self, args, from_tty):
>> + mode = args
>> + str = "<" + "-" * 78 + ">"
>> + for i in range(0, 20):
>> + for color_name in basic_colors:
>> + c = gdb.Color(color_name)
>> + write(mode, c.escape_sequence(True))
>> + write(mode, str)
>> +
>> + default = gdb.Color("none")
>> + write(mode, default.escape_sequence(True))
>> + write(mode, "\n")
>> +
>> +
>> +ColorTester()
>> diff --git a/gdb/utils.c b/gdb/utils.c
>> index 10d3d51e481..92e626a9c75 100644
>> --- a/gdb/utils.c
>> +++ b/gdb/utils.c
>> @@ -1702,10 +1702,25 @@ pager_file::puts (const char *linebuffer)
>> else if (*linebuffer == '\033'
>> && skip_ansi_escape (linebuffer, &skip_bytes))
>> {
>> - m_wrap_buffer.append (linebuffer, skip_bytes);
>> - /* Note that we don't consider this a character, so we
>> + /* We don't consider escape sequences as characters, so we
>> don't increment chars_printed here. */
>> - linebuffer += skip_bytes;
>> +
>> + size_t style_len;
>> + ui_file_style style;
>> + if (style.parse (linebuffer, &style_len)
>> + && style_len <= skip_bytes)
>> + {
>> + this->emit_style_escape (style);
>> +
>> + linebuffer += style_len;
>> + skip_bytes -= style_len;
>> + }
>> +
>> + if (skip_bytes > 0)
>> + {
>> + m_wrap_buffer.append (linebuffer, skip_bytes);
>> + linebuffer += skip_bytes;
>> + }
>> }
>> else if (*linebuffer == '\r')
>> {
>> --
>> 2.47.1
>
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCH] gdb/testsuite: work around empty substring bug in expect
2025-08-25 10:21 ` Tom de Vries
@ 2025-08-26 14:35 ` Andrew Burgess
2025-08-27 6:24 ` Tom de Vries
0 siblings, 1 reply; 61+ messages in thread
From: Andrew Burgess @ 2025-08-26 14:35 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess, Tom de Vries, Guinevere Larsen
There is a bug in expect, see:
https://sourceforge.net/p/expect/patches/26/
which causes empty substring matches from a regexp to instead return
the complete input buffer. To reproduce this bug, try this command:
expect -c 'spawn sh -c "echo -n -e \"abc\""; \
expect -re "(a?)(a)(bc)"; \
puts "\n"; \
for { set i 1 } { $i < 4 } { incr i } { \
puts -nonewline "($i): \""; \
puts -nonewline $expect_out($i,string); \
puts "\"" \
}'
For a working expect the output looks like:
spawn sh -c echo -n -e "abc"
abc
(1): ""
(2): "a"
(3): "bc"
But for a broken expect the output looks like:
spawn sh -c echo -n -e "abc"
abc
(1): "abc"
(2): "a"
(3): "bc"
Notice that (1) is now returning the complete input buffer rather than
the empty string, this is wrong.
This is not the first time this bug has impacted GDB's testsuite,
this commit seems to be working around the same problem:
commit e579b537353cd91cb8fac1eaeb69901d4936766f
Date: Sat Aug 16 20:32:37 2025 +0200
[gdb/testsuite] Fix TUI tests on freebsd
I recently pushed this commit:
commit 3825c972a636852600b47c242826313f4b9963b8
Date: Wed Jun 18 15:02:29 2025 +0100
gdb: allow gdb.Color to work correctly with pagination
Which added gdb.python/py-color-pagination.exp. Bug PR gdb/33321 was
then created as the test was failing on some hosts. Turns out, this
is same expect bug.
The fix presented here is the same as for e579b537353cd91cb8, avoid
using optional regexp substrings at the start of a regexp, and instead
use two separate regexp patterns. With this change in place, the test
now passes on all hosts.
There's no change in what is being tested after this commit.
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33321
---
| 36 +++++++++++++------
1 file changed, 25 insertions(+), 11 deletions(-)
--git a/gdb/testsuite/gdb.python/py-color-pagination.exp b/gdb/testsuite/gdb.python/py-color-pagination.exp
index 3235fffe5cc..e1ca1f1ce75 100644
--- a/gdb/testsuite/gdb.python/py-color-pagination.exp
+++ b/gdb/testsuite/gdb.python/py-color-pagination.exp
@@ -66,18 +66,25 @@ proc test_pagination { type mode } {
exp_continue
}
- -re "^(${::any_color}?)(${::any_color})$::str" {
+ -re "^(${::any_color})(${::any_color})$::str" {
# After a continuation prompt GDB will restore the previous
# color, and then we immediately switch to a new color.
set restored_color $expect_out(1,string)
- if { $restored_color ne ""
- && $restored_color ne $expected_restore_color } {
+ if { $restored_color ne $expected_restore_color } {
set saw_bad_color_handling true
}
set last_color $expect_out(2,string)
exp_continue
}
+ -re "^(${::any_color})$::str" {
+ # This pattern matches printing STR in all cases that are not
+ # immediately after a pagination prompt. In this case there is
+ # a single escape sequence to set the color.
+ set last_color $expect_out(1,string)
+ exp_continue
+ }
+
-re "^\033\\\[${::decimal}m$::str" {
# This catches the case where the color's escape sequence has
# not been converted back into a full style. This indicates
@@ -86,18 +93,25 @@ proc test_pagination { type mode } {
exp_continue
}
- -re "^((?:\033\\\[m)?)$::pagination_prompt$" {
+ -re "^\033\\\[m$::pagination_prompt$" {
# After a pagination prompt we expect GDB to restore the last
# color.
set expected_restore_color $last_color
- # If we didn't see a color reset sequence then the pagination
- # prompt will have been printed in the wrong color, this is a
- # GDB bug.
- set color_reset $expect_out(1,string)
- if { $color_reset eq "" } {
- set saw_bad_color_handling true
- }
+ # Send '\n' to view more output.
+ send_gdb "\n"
+ exp_continue
+ }
+
+ -re "^$::pagination_prompt$" {
+ # After a pagination prompt we expect GDB to restore the last
+ # color.
+ set expected_restore_color $last_color
+
+ # If we didn't see a color reset sequence before the pagination
+ # prompt, then the prompt will have been printed in the wrong
+ # color, this is a GDB bug.
+ set saw_bad_color_handling true
# Send '\n' to view more output.
send_gdb "\n"
base-commit: 3825c972a636852600b47c242826313f4b9963b8
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* Re: [PATCH] gdb/testsuite: work around empty substring bug in expect
2025-08-26 14:35 ` [PATCH] gdb/testsuite: work around empty substring bug in expect Andrew Burgess
@ 2025-08-27 6:24 ` Tom de Vries
0 siblings, 0 replies; 61+ messages in thread
From: Tom de Vries @ 2025-08-27 6:24 UTC (permalink / raw)
To: Andrew Burgess, gdb-patches; +Cc: Guinevere Larsen
On 8/26/25 16:35, Andrew Burgess wrote:
> There is a bug in expect, see:
>
> https://sourceforge.net/p/expect/patches/26/
>
> which causes empty substring matches from a regexp to instead return
> the complete input buffer. To reproduce this bug, try this command:
>
> expect -c 'spawn sh -c "echo -n -e \"abc\""; \
> expect -re "(a?)(a)(bc)"; \
> puts "\n"; \
> for { set i 1 } { $i < 4 } { incr i } { \
> puts -nonewline "($i): \""; \
> puts -nonewline $expect_out($i,string); \
> puts "\"" \
> }'
>
> For a working expect the output looks like:
>
> spawn sh -c echo -n -e "abc"
> abc
>
> (1): ""
> (2): "a"
> (3): "bc"
>
> But for a broken expect the output looks like:
>
> spawn sh -c echo -n -e "abc"
> abc
>
> (1): "abc"
> (2): "a"
> (3): "bc"
>
> Notice that (1) is now returning the complete input buffer rather than
> the empty string, this is wrong.
>
> This is not the first time this bug has impacted GDB's testsuite,
> this commit seems to be working around the same problem:
>
> commit e579b537353cd91cb8fac1eaeb69901d4936766f
> Date: Sat Aug 16 20:32:37 2025 +0200
>
> [gdb/testsuite] Fix TUI tests on freebsd
>
> I recently pushed this commit:
>
> commit 3825c972a636852600b47c242826313f4b9963b8
> Date: Wed Jun 18 15:02:29 2025 +0100
>
> gdb: allow gdb.Color to work correctly with pagination
>
> Which added gdb.python/py-color-pagination.exp. Bug PR gdb/33321 was
> then created as the test was failing on some hosts. Turns out, this
> is same expect bug.
>
> The fix presented here is the same as for e579b537353cd91cb8, avoid
> using optional regexp substrings at the start of a regexp, and instead
> use two separate regexp patterns. With this change in place, the test
> now passes on all hosts.
>
> There's no change in what is being tested after this commit.
>
Hi Andrew,
thanks for digging into this.
I've tested this patch on Tumbleweed, and it fixed the problem for me there.
The patch LGTM.
Approved-By: Tom de Vries <tdevries@suse.de>
- Tom
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33321
> ---
> .../gdb.python/py-color-pagination.exp | 36 +++++++++++++------
> 1 file changed, 25 insertions(+), 11 deletions(-)
>
> diff --git a/gdb/testsuite/gdb.python/py-color-pagination.exp b/gdb/testsuite/gdb.python/py-color-pagination.exp
> index 3235fffe5cc..e1ca1f1ce75 100644
> --- a/gdb/testsuite/gdb.python/py-color-pagination.exp
> +++ b/gdb/testsuite/gdb.python/py-color-pagination.exp
> @@ -66,18 +66,25 @@ proc test_pagination { type mode } {
> exp_continue
> }
>
> - -re "^(${::any_color}?)(${::any_color})$::str" {
> + -re "^(${::any_color})(${::any_color})$::str" {
> # After a continuation prompt GDB will restore the previous
> # color, and then we immediately switch to a new color.
> set restored_color $expect_out(1,string)
> - if { $restored_color ne ""
> - && $restored_color ne $expected_restore_color } {
> + if { $restored_color ne $expected_restore_color } {
> set saw_bad_color_handling true
> }
> set last_color $expect_out(2,string)
> exp_continue
> }
>
> + -re "^(${::any_color})$::str" {
> + # This pattern matches printing STR in all cases that are not
> + # immediately after a pagination prompt. In this case there is
> + # a single escape sequence to set the color.
> + set last_color $expect_out(1,string)
> + exp_continue
> + }
> +
> -re "^\033\\\[${::decimal}m$::str" {
> # This catches the case where the color's escape sequence has
> # not been converted back into a full style. This indicates
> @@ -86,18 +93,25 @@ proc test_pagination { type mode } {
> exp_continue
> }
>
> - -re "^((?:\033\\\[m)?)$::pagination_prompt$" {
> + -re "^\033\\\[m$::pagination_prompt$" {
> # After a pagination prompt we expect GDB to restore the last
> # color.
> set expected_restore_color $last_color
>
> - # If we didn't see a color reset sequence then the pagination
> - # prompt will have been printed in the wrong color, this is a
> - # GDB bug.
> - set color_reset $expect_out(1,string)
> - if { $color_reset eq "" } {
> - set saw_bad_color_handling true
> - }
> + # Send '\n' to view more output.
> + send_gdb "\n"
> + exp_continue
> + }
> +
> + -re "^$::pagination_prompt$" {
> + # After a pagination prompt we expect GDB to restore the last
> + # color.
> + set expected_restore_color $last_color
> +
> + # If we didn't see a color reset sequence before the pagination
> + # prompt, then the prompt will have been printed in the wrong
> + # color, this is a GDB bug.
> + set saw_bad_color_handling true
>
> # Send '\n' to view more output.
> send_gdb "\n"
>
> base-commit: 3825c972a636852600b47c242826313f4b9963b8
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv8 0/3] new gdb.Style API
2025-08-13 10:25 ` [PATCHv7 0/4] Fix for gdb.Color + pagination AND new gdb.Style API Andrew Burgess
` (3 preceding siblings ...)
2025-08-13 10:34 ` [PATCHv7 3/4] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
@ 2025-08-27 11:34 ` Andrew Burgess
2025-08-27 11:34 ` [PATCHv8 1/3] gdb/python: add gdb.Style class Andrew Burgess
` (3 more replies)
4 siblings, 4 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-08-27 11:34 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
After the gdb.Color support was added to the Python API I wondered if
this would be enough to easily add styling to custom Python commands.
But I don't think it is, there's still lots that would need to be
handled in user code, for example, the intensity setting. And having
styles that track named styles (e.g. if I want to apply the 'filename'
style to some text from Python).
So this series builds on top of the gdb.Color API to provide a new
gdb.Style API which, I hope, makes it easy to start writing Python
commands that can use GDB's styles.
Here's an example of a simple custome command that uses styles:
import os
class user_home_cmd(gdb.Command):
def __init__(self):
super().__init__("user-home", gdb.COMMAND_USER)
self._style = gdb.Style('filename')
def invoke(self, args, from_tty):
print("The filename is %s."
% (self._style.apply(os.environ['HOME'])))
user_home_cmd()
The new 'user-home' command prints the current users home directory
using 'filename' style. Changing the 'filename' style and re-running
the command will track the style changes.
In v2:
- Fixed an issue with the py-style-parameter-set.exp test; GDB was
not started in a terminal with styling support in one case, so
tests would fail depending on how the testsuite was being run.
In v3:
- Another test issue. This time, the error message given when
parsing an argument of the wrong type changes with Python version.
Relax the regexp to accept any 'TypeError' message.
In v4:
- Two new patches. First patch extends pager_file so that
gdb.Color, and gdb.Style when it's added can work well with GDB's
pager. Last patch extends the gdb.write() Python function to also
support styling.
- I've renamed some of the classes in python/py-style.c, nothing
major, I replaced 'stylepy' with 'style' in some names. I think
the new names are clearer, and better match the rest of GDB's
Python API code.
- Unfortunately, the new pager_file changes mean that this patch
series now depends on this other patch:
https://inbox.sourceware.org/gdb-patches/444008aeae2bb3c68cf868fa317374b3d7973860.1750197766.git.aburgess@redhat.com
Without that patch there will be some failures in
gdb.python/py-color-pagination.exp. I'm not proposing to merge
this until that other patch, or something like it, is merged. But
this series could be reviewed independently.
In v5:
- The pager_file styling fixes mentioned for v4 have now been merged
to master. I've rebased this series on top of them, and all the
tests are now passing.
- I've tweaked the wording for a couple of the commit messages just
to make things clearer.
- No real code changes.
In v6:
- Rebased as there were some style related changes merged to master
recently. No conflicts, and everything still works fine, so there
are no real changes here.
In v7:
- Rebased as there were more style related changes to master which
caused some merged conflicts, and test failures. Both turned out
to be trivial to fix.
In v8:
- I merged the gdb.Color + pagination fix, this leaves the 3 patches
related to the gdb.Style API.
- I've updated the tests in patch 3 to take commit e17e65798e4 into
account.
- No updates needed to the actual GDB changes.
Thanks,
Andrew
---
Andrew Burgess (3):
gdb/python: add gdb.Style class
gdb/python: new class gdb.StyleParameterSet
gdb/python: extend gdb.write to support styled output
gdb/Makefile.in | 1 +
gdb/NEWS | 14 +
gdb/doc/python.texi | 262 +++++-
gdb/python/lib/gdb/__init__.py | 209 +++++
gdb/python/py-style.c | 824 ++++++++++++++++++
gdb/python/python-internal.h | 15 +
gdb/python/python.c | 30 +-
.../gdb.python/py-color-pagination.exp | 61 +-
.../gdb.python/py-color-pagination.py | 36 +
.../gdb.python/py-style-parameter-set.exp | 366 ++++++++
gdb/testsuite/gdb.python/py-style.exp | 371 ++++++++
gdb/ui-style.h | 6 +
12 files changed, 2187 insertions(+), 8 deletions(-)
create mode 100644 gdb/python/py-style.c
create mode 100644 gdb/testsuite/gdb.python/py-style-parameter-set.exp
create mode 100644 gdb/testsuite/gdb.python/py-style.exp
base-commit: e17e65798e466913f384e7a568d991124798ecb4
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv8 1/3] gdb/python: add gdb.Style class
2025-08-27 11:34 ` [PATCHv8 0/3] new gdb.Style API Andrew Burgess
@ 2025-08-27 11:34 ` Andrew Burgess
2025-09-16 16:47 ` Tom Tromey
2025-08-27 11:34 ` [PATCHv8 2/3] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
` (2 subsequent siblings)
3 siblings, 1 reply; 61+ messages in thread
From: Andrew Burgess @ 2025-08-27 11:34 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess, Eli Zaretskii
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 ++++++++++++++++++
| 5 +-
| 20 +
gdb/testsuite/gdb.python/py-style.exp | 342 ++++++++
gdb/ui-style.h | 6 +
8 files changed, 1316 insertions(+), 2 deletions(-)
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 7654fb1aa04..760eacec97a 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 db0e74a2623..f2239af831e 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -137,6 +137,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 6fa22851f6a..0b4ed9d6a1c 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.
@@ -7262,6 +7263,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..b3983af3a85
--- /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 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;
+
+ 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)
+{
+ 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;
+ (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)
+{
+ 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);
--git a/gdb/testsuite/gdb.python/py-color-pagination.exp b/gdb/testsuite/gdb.python/py-color-pagination.exp
index e7a9e4fec5f..1974dbb4186 100644
--- a/gdb/testsuite/gdb.python/py-color-pagination.exp
+++ b/gdb/testsuite/gdb.python/py-color-pagination.exp
@@ -14,7 +14,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# This file is part of the GDB testsuite. It tests gdb.Color and how this
-# interacts with GDB's pagination system.
+# interacts with GDB's pagination system. The test also tests gdb.Style
+# because the tests are very similar.
load_lib gdb-python.exp
@@ -130,7 +131,7 @@ proc test_pagination { type mode } {
}
}
-foreach_with_prefix type { color } {
+foreach_with_prefix type { color style } {
foreach_with_prefix mode { write print } {
test_pagination $type $mode
}
--git a/gdb/testsuite/gdb.python/py-color-pagination.py b/gdb/testsuite/gdb.python/py-color-pagination.py
index efd501eedf5..f0252e5bf86 100644
--- a/gdb/testsuite/gdb.python/py-color-pagination.py
+++ b/gdb/testsuite/gdb.python/py-color-pagination.py
@@ -43,4 +43,24 @@ class ColorTester(gdb.Command):
write(mode, "\n")
+class StyleTester(gdb.Command):
+ def __init__(self):
+ super().__init__("style-fill", gdb.COMMAND_USER)
+
+ def invoke(self, args, from_tty):
+ mode = args
+ str = "<" + "-" * 78 + ">"
+ for i in range(0, 20):
+ for color_name in basic_colors:
+ c = gdb.Color(color_name)
+ s = gdb.Style(foreground=c)
+ write(mode, s.escape_sequence())
+ write(mode, str)
+
+ default = gdb.Style()
+ write(mode, default.escape_sequence())
+ write(mode, "\n")
+
+
ColorTester()
+StyleTester()
diff --git a/gdb/testsuite/gdb.python/py-style.exp b/gdb/testsuite/gdb.python/py-style.exp
new file mode 100644
index 00000000000..b2efe9a97e3
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-style.exp
@@ -0,0 +1,342 @@
+# 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"
diff --git a/gdb/ui-style.h b/gdb/ui-style.h
index 1ea3556869d..64f710b0cc1 100644
--- a/gdb/ui-style.h
+++ b/gdb/ui-style.h
@@ -334,6 +334,12 @@ struct ui_file_style
return m_intensity;
}
+ /* Set the intensity of this style. */
+ void set_intensity (intensity i)
+ {
+ m_intensity = i;
+ }
+
/* Return true if this style specified italic display; false
otherwise. */
bool is_italic () const
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv8 2/3] gdb/python: new class gdb.StyleParameterSet
2025-08-27 11:34 ` [PATCHv8 0/3] new gdb.Style API Andrew Burgess
2025-08-27 11:34 ` [PATCHv8 1/3] gdb/python: add gdb.Style class Andrew Burgess
@ 2025-08-27 11:34 ` Andrew Burgess
2025-09-16 16:50 ` Tom Tromey
2025-08-27 11:34 ` [PATCHv8 3/3] gdb/python: extend gdb.write to support styled output Andrew Burgess
2025-09-23 16:54 ` [PATCHv9 0/3] new gdb.Style API Andrew Burgess
3 siblings, 1 reply; 61+ messages in thread
From: Andrew Burgess @ 2025-08-27 11:34 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess, Eli Zaretskii
Add a new helper class gdb.StyleParameterSet. This new class can be
used to simplify creation of new style parameter sets. A style
parameter set is the 'foreground', 'background', and (optionally), the
'intensity' settings, all grouped under a single prefix command.
And example usage is:
(gdb) python s = gdb.StyleParameterSet("my-style")
(gdb) show style my-style
style my-style background: The "my-style" style background color is: none
style my-style foreground: The "my-style" style foreground color is: none
style my-style intensity: The "my-style" style display intensity is: normal
(gdb)
Having created a gdb.StyleParameterSet, the object itself can be used
to access a named style corresponding to the setting group, like this:
(gdb) python print(s.style)
<gdb.Style name='my-style', fg=none, bg=none, intensity=normal>
(gdb)
Of course, having access to the gdb.Style makes it easy to change the
settings, or the settings can be adjusted via the normal CLI 'set'
commands.
As gdb.StyleParameterSet manages a set of parameters, and the
gdb.Parameter class uses Parameter.value as the attribute to read the
parameter's value, there is also StyleParameterSet.value, but this is
just an alias for StyleParameterSet.style, that is, it allows the
gdb.Style object to be read and written too.
It is worth noting that this class only creates a single level of
prefix command. As an example GDB has style 'disassembler mnemonic',
where the 'disassembler' part is a group of related styles. If a user
wanted to create:
style
my-style-group
style-1
style-2
style-3
Where each of 'style-1', 'style-2', and 'style-3' will have the full
set of 'foreground', 'background', and 'intensity', then the
gdb.StyleParameterSet can be used to create the 'style-N' part, but
the user will have to create the 'my-style-group' prefix themselves,
possibly using gdb.ParameterPrefix, e.g.:
gdb.ParameterPrefix("style my-style-group", gdb.COMMAND_NONE)
gdb.StyleParameterSet("my-style-group style-1")
gdb.StyleParameterSet("my-style-group style-2")
gdb.StyleParameterSet("my-style-group style-3")
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
---
gdb/NEWS | 4 +
gdb/doc/python.texi | 115 +++++-
gdb/python/lib/gdb/__init__.py | 209 ++++++++++
.../gdb.python/py-style-parameter-set.exp | 366 ++++++++++++++++++
4 files changed, 693 insertions(+), 1 deletion(-)
create mode 100644 gdb/testsuite/gdb.python/py-style-parameter-set.exp
diff --git a/gdb/NEWS b/gdb/NEWS
index f2239af831e..6539c917652 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -144,6 +144,10 @@ info threads [-gid] [-stopped] [-running] [ID]...
gdb.INTENSITY_DIM for use with gdb.Style when representing
intensities.
+ ** New gdb.StyleParameterSet for creating custom style settings.
+ Use gdb.StyleParameterSet(NAME) to create 'set style NAME ...'
+ and 'show style NAME ...' parameters.
+
** 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 0b4ed9d6a1c..cc033d1bccd 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -5293,6 +5293,7 @@ Parameters In Python
feature-2}, then the @kbd{plugin-name} would need to be a prefix
command (@pxref{CLI Commands In Python}).
+@anchor{gdb.ParameterPrefix}
However, when creating parameters, you will almost always need to
create two prefix commands, one as a @kbd{set} sub-command, and one as
a @kbd{show} sub-command. @value{GDBN} provides the
@@ -5379,6 +5380,117 @@ Parameters In Python
ExampleParam("plugin-name feature-2")
@end smallexample
+@anchor{Creating Style Parameters}
+The helper class @code{gdb.StyleParameterSet} exists to make it easier
+to create new styles. @value{GDBN} places style settings under
+@samp{show style @dots{}} and @samp{set style @dots{}}, an example of a style
+is @samp{filename}. Each style is really a prefix command (@pxref{CLI
+Commands In Python}), with sub-commands @samp{foreground},
+@samp{background}, and optionally, @samp{intensity}.
+
+It is simple enough to create a new style using two @code{gdb.Command}
+objects for the prefix commands (one for @samp{set}, and one for
+@samp{show}), and three @code{gdb.Parameter} objects, one each for the
+@samp{foreground}, @samp{background}, and @samp{intensity}. You would
+also want to take care to craft the help text so that the new style
+behaves the same as the existing styles.
+
+Or, you can use the @code{gdb.StyleParameterSet} class, which takes
+care of all this, as the following example shows:
+
+@smallexample
+@group
+(@value{GDBP}) python s = gdb.StyleParameterSet("my-style")
+(@value{GDBP}) show style my-style
+style my-style background: The "my-style" style background color is: none
+style my-style foreground: The "my-style" style foreground color is: none
+style my-style intensity: The "my-style" style display intensity is: normal
+(@value{GDBP})
+@end group
+@end smallexample
+
+You might also want to group a number of styles within a new prefix,
+similar to how @value{GDBN} groups disassembler related styles within
+the @samp{style disassembler} prefix. This can be done using
+@code{gdb.ParameterPrefix} (@pxref{gdb.ParameterPrefix}), as in this
+example:
+
+@smallexample
+@group
+(@value{GDBP}) python gdb.ParameterPrefix("style group", gdb.COMMAND_NONE)
+(@value{GDBP}) python s_a = gdb.StyleParameterSet("group aa")
+(@value{GDBP}) python s_b = gdb.StyleParameterSet("group bb")
+(@value{GDBP}) show style group
+style group aa background: The "group aa" style background color is: none
+style group aa foreground: The "group aa" style foreground color is: none
+style group aa intensity: The "group aa" style display intensity is: normal
+style group bb background: The "group bb" style background color is: none
+style group bb foreground: The "group bb" style foreground color is: none
+style group bb intensity: The "group bb" style display intensity is: normal
+(@value{GDBP})
+@end group
+@end smallexample
+
+The @code{gdb.StyleParameterSet} class has the following methods and
+attributes:
+
+@defun StyleParameterSet.__init__(name, @w{add_intensity=@code{True}}, @w{doc=@code{None}})
+Create a new style group based on @var{name}, which is a string. For
+example if @var{name} is @samp{my-style}, then @value{GDBN} will
+create the prefix commands @samp{set style my-style} and @samp{show
+style my-style}. Within these prefix commands will be
+@samp{foreground}, @samp{background}, and @samp{intensity} parameters
+with the appropriate types.
+
+It is also possible for @var{name} to consist of multiple words, so
+long as each prefix command (except the last one) already exists. For
+example, it is valid to use a @var{name} value of @samp{disassembler
+my-style}, as the @samp{disassembler} prefix command already exists.
+@value{GDBN} would then create @samp{set style disassembler my-style}
+and @samp{show style disassembler my-style}, and within the
+@samp{my-style} prefixes will be the @samp{foreground},
+@samp{background}, and @samp{intensity} parameters with the
+appropriate types.
+
+Every style requires a @samp{foreground} and @samp{background}, but
+not every style needs an @samp{intensity}. If @var{add_intensity} is
+@code{True} (the default), then the @samp{intensity} parameter will be
+created. If @var{add_intensity} is @code{False}, then the
+@samp{intensity} parameter will not be created.
+
+If the @samp{intensity} parameter is not created, then the
+@code{gdb.Style} (@pxref{Styles In Python}) created from this
+@code{gdb.StyleParameterSet} will have @code{gdb.INTENSITY_NORMAL}.
+
+The @var{doc} should be a string which will be used as the help text
+for the @var{name} prefix command. This text is used as the
+@code{Command.__doc__} value for the @code{gdb.Command} object that is
+the prefix command object (@pxref{CLI Commands In Python}). If
+@var{doc} is @code{None} (the default) then a basic default help text
+is used.
+@end defun
+
+@defun StyleParameterSet.apply(string)
+Equivalent to @code{StyleParameterSet.style.apply(string)}. Returns a
+copy of @var{string} with escape sequences added to the start and end.
+The escape sequence at the start applies this style, and the escape
+sequence at the end restores the terminal default.
+
+If styling is disabled (i.e.@: @samp{set style enabled off}), then no
+escape sequences are added and this method returns a copy of
+@var{string}.
+@end defun
+
+@defvar StyleParameterSet.style
+This read/write attribute holds a @code{gdb.Style} object
+(@pxref{Styles In Python}), that is a named style associated with this
+style parameter group.
+@end defvar
+
+@defvar StyleParameterSet.value
+This is an alias for @code{StyleParameterSet.style}, see above.
+@end defvar
+
@node Functions In Python
@subsubsection Writing new convenience functions
@@ -7275,7 +7387,8 @@ Styles In Python
@value{GDBN} has many styles builtin (@pxref{Output Styling}), and
style objects can be created that apply these builtin styles to Python
-output.
+output. It is also possible to create new styles which can be used to
+style Python output (@pxref{Creating Style Parameters}).
The style class is called @code{gdb.Style}, and has the following
methods and attributes:
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index cedd897ab0f..f2f57280007 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -515,3 +515,212 @@ class ParameterPrefix:
def dont_repeat(self):
if self.active_prefix is not None:
self.active_prefix.dont_repeat()
+
+
+class StyleParameterSet:
+ """Create new style parameters.
+
+ A style parameter is a set of parameters that start with 'set style ...'
+ and 'show style ...'. For example 'filename' is a style parameter, and
+ 'disassembler symbol' is another style parameter.
+
+ The name of the style parameter is really a prefix command. Under this
+ we must have two commands 'foreground' and 'background', which are color
+ parameters. A third, optional command 'intensity', is an enum with
+ values 'normal', 'bold', and 'dim'.
+
+ A StyleParameterSet is initialised with a name, e.g. 'filename' or
+ 'disassembler symbol'. The StyleParameterSet creates the prefix
+ commands in the 'set style' and 'show style' name space, and then adds
+ the 'foreground', 'background', and optionally, the 'intensity'
+ commands.
+
+ If you want a whole new style group, similar to 'disassembler',
+ then you need to add this yourself first, then StyleParameterSet
+ can be used to create styles within the new prefix group.
+
+ The 'value' attribute on this object can be used to get and set a
+ gdb.Style object which controls all aspects of this style.
+
+ For readability, the alias 'style' is the same as 'value'.
+ """
+
+ def __init__(self, name, add_intensity=True, doc=None):
+ # The STYLE_NAME is something like 'filename' is 'set style
+ # filename ...', and PARAM_NAME is one of 'foreground',
+ # 'background', or 'intensity'. The DESC_TEXT is the long
+ # form used in help text, like 'foreground color' or 'display
+ # intensity'. The DEFAULT_VALUE is used to set the SELF.value
+ # attribute. And PARAM_TYPE is a gdb.PARAM_* constant. The
+ # ARGS is used for gdb.PARAM_ENUM, which ARGS should be the
+ # enum value list.
+ class style_parameter(Parameter):
+ def __init__(
+ self,
+ style_name,
+ parent_obj,
+ param_name,
+ desc_text,
+ default_value,
+ param_type,
+ *args
+ ):
+ # Setup documentation must be done before calling
+ # parent's __init__ method, as the __init__ reads (and
+ # copies) these values.
+ self.show_doc = "Show the " + desc_text + " for this property."
+ self.set_doc = "Set the " + desc_text + " for this property."
+ self.__doc__ = ""
+
+ # Call the parent's __init__ method to actually create
+ # the parameter.
+ super().__init__(
+ "style " + style_name + " " + param_name,
+ COMMAND_NONE,
+ param_type,
+ *args
+ )
+
+ # Store information we need in other methods.
+ self._style_name = style_name
+ self._desc_text = desc_text
+ self._parent = parent_obj
+
+ # Finally, setup the default value.
+ self.value = default_value
+
+ # Return the 'show style <style-name> <attribute>' string,
+ # which has styling applied.
+ def get_show_string(self, value):
+ s = self._parent.style
+ return (
+ "The "
+ + s.apply('"' + self._style_name + '" style')
+ + " "
+ + self._desc_text
+ + " is: "
+ + value
+ )
+
+ class style_foreground_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "foreground",
+ "foreground color",
+ Color(),
+ PARAM_COLOR,
+ )
+
+ class style_background_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "background",
+ "background color",
+ Color(),
+ PARAM_COLOR,
+ )
+
+ class style_intensity_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "intensity",
+ "display intensity",
+ "normal",
+ PARAM_ENUM,
+ ["normal", "bold", "dim"],
+ )
+
+ if doc is None:
+ doc = (
+ "The "
+ + name
+ + " display styling.\nConfigure "
+ + name
+ + " colors and display intensity."
+ )
+
+ ParameterPrefix("style " + name, COMMAND_NONE, doc)
+ self._foreground = style_foreground_parameter(name, self)
+ self._background = style_background_parameter(name, self)
+ if add_intensity:
+ self._intensity = style_intensity_parameter(name, self)
+ self._name = name
+ self._style = None
+
+ @property
+ def value(self):
+ """Return the gdb.Style object for this parameter set."""
+ if self._style is None:
+ self._style = Style(self._name)
+ return self._style
+
+ @property
+ def style(self):
+ """Return the gdb.Style object for this parameter set.
+
+ This is an alias for self.value."""
+ return self.value
+
+ @value.setter
+ def value(self, new_value):
+ """Set this parameter set to NEW_VALUE, a gdb.Style object.
+
+ The attributes of NEW_VALUE are used to update the current settings
+ of this parameter set. If this parameter set was created without
+ an intensity setting, then the intensity of NEW_VALUE is ignored."""
+ if not isinstance(new_value, Style):
+ raise TypeError("value must be gdb.Style, not %s" % type(new_value))
+ self._foreground.value = new_value.foreground
+ self._background.value = new_value.background
+ if hasattr(self, "_intensity"):
+ intensity_value = new_value.intensity
+ if intensity_value == INTENSITY_BOLD:
+ intensity_string = "bold"
+ elif intensity_value == INTENSITY_DIM:
+ intensity_string = "dim"
+ elif intensity_value == INTENSITY_NORMAL:
+ intensity_string = "normal"
+ else:
+ raise ValueError(
+ "unknown intensity value %d from Style" % intensity_value
+ )
+
+ self._intensity.value = intensity_string
+
+ @style.setter
+ def style(self, new_value):
+ """Set this parameter set to NEW_VALUE, a gdb.Style object.
+
+ This is an alias for self.value."""
+ self.value = new_value
+
+ def apply(self, *args, **kwargs):
+ """Apply this style to the arguments.
+
+ Forwards all arguments to self.style.apply(). The arguments should be a string,
+ to which this style is applied. This function returns the same string with
+ escape sequences added to apply this style.
+
+ If styling is globally disabled ('set style enabled off') then no escape sequences
+ will be addedded, the input string is returned."""
+ return self.style.apply(*args, **kwargs)
+
+ def __repr__(self):
+ """A string representation of SELF."""
+
+ def full_typename(obj):
+ module = type(obj).__module__
+ qualname = type(obj).__qualname__
+
+ if module is None or module == "builtins":
+ return qualname
+ else:
+ return module + "." + qualname
+
+ return "<" + full_typename(self) + " name='" + self._name + "'>"
diff --git a/gdb/testsuite/gdb.python/py-style-parameter-set.exp b/gdb/testsuite/gdb.python/py-style-parameter-set.exp
new file mode 100644
index 00000000000..73d94d1bb51
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-style-parameter-set.exp
@@ -0,0 +1,366 @@
+# 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.StyleParameterSet.
+
+load_lib gdb-python.exp
+
+require allow_python_tests
+
+# Create a regexp that can be used to match the output of a 'show style
+# NAME' command. HAS_INTENSITY is a boolean and indicates if style NAME has
+# an intensity attribute.
+proc gen_show_style_re { name has_intensity } {
+ set output \
+ [list \
+ "style ${name} background: The \"${name}\" style background color is: none" \
+ "style ${name} foreground: The \"${name}\" style foreground color is: none"]
+
+ if { $has_intensity } {
+ lappend output \
+ "style ${name} intensity: The \"${name}\" style display intensity is: normal"
+ }
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'set style NAME' command,
+# that is, a 'set' command that doesn't actually set an attribute of a
+# style, in this case GDB will print some useful help text. HAS_INTENSITY is
+# a boolean and indicates if style NAME has an intensity attribute or not.
+proc gen_set_style_re { name has_intensity } {
+ set output \
+ [list \
+ "List of \"set style $name\" subcommands:" \
+ "" \
+ "set style $name background -- Set the background color for this property\\." \
+ "set style $name foreground -- Set the foreground color for this property\\."]
+
+ if { $has_intensity } {
+ lappend output \
+ "set style $name intensity -- Set the display intensity for this property\\."
+ }
+
+ lappend output \
+ "" \
+ "Type \"help set style $name\" followed by subcommand name for full documentation\\." \
+ "Type \"apropos word\" to search for commands related to \"word\"\\." \
+ "Type \"apropos -v word\" for full documentation of commands related to \"word\"\\." \
+ "Command name abbreviations are allowed if unambiguous\\."
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'help show style NAME'
+# command. HAS_INTENSITY is a boolean and indicates if style NAME has an
+# intensity attribute or not. DOC is a regexp that matches the doc string
+# for style NAME.
+proc gen_help_show_style_re { name has_intensity doc } {
+ set output \
+ [list \
+ $doc \
+ "" \
+ "List of \"show style ${name}\" subcommands:" \
+ "" \
+ "show style $name background -- Show the background color for this property\\." \
+ "show style $name foreground -- Show the foreground color for this property\\."]
+ if { $has_intensity } {
+ lappend output \
+ "show style $name intensity -- Show the display intensity for this property\\."
+ }
+
+ lappend output \
+ "" \
+ "Type \"help show style $name\" followed by subcommand name for full documentation\\." \
+ "Type \"apropos word\" to search for commands related to \"word\"\\." \
+ "Type \"apropos -v word\" for full documentation of commands related to \"word\"\\." \
+ "Command name abbreviations are allowed if unambiguous\\."
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'help set style NAME'
+# command. HAS_INTENSITY is a boolean and indicates if style NAME has an
+# intensity attribute or not. DOC is a regexp that matches the doc string
+# for style NAME.
+proc gen_help_set_style_re { name has_intensity doc } {
+ return \
+ [multi_line \
+ $doc \
+ "" \
+ [gen_set_style_re $name $has_intensity]]
+}
+
+# Create styles with and without intensity. Use named and unnamed
+# argument passing, and vary the argument passing order. Create
+# styles with and without documentation.
+#
+# Confirm that the styles contain the expected sub-commands, and that
+# the documentation is as expected.
+proc_with_prefix test_basic_usage {} {
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"style-1\")" \
+ "create style-1"
+
+ gdb_test_no_output \
+ "python style_2 = gdb.StyleParameterSet(add_intensity=True, name=\"style-2\")" \
+ "create style-2"
+
+ gdb_test_no_output \
+ "python style_3 = gdb.StyleParameterSet(name=\"style-3\", doc=\"Style-3 display styling.\\nThis is a multi-line documentation\\nstring describing style-3.\")" \
+ "create style-3"
+
+ gdb_test_no_output \
+ "python style_4 = gdb.StyleParameterSet(\"style-4\", add_intensity=False)" \
+ "create style-4"
+
+ foreach style { style-1 style-2 style-3 } {
+ gdb_test "show style $style" \
+ [gen_show_style_re $style true]
+ gdb_test "set style $style" \
+ [gen_set_style_re $style true]
+ }
+
+ gdb_test "show style style-4" \
+ [gen_show_style_re "style-4" false]
+
+ foreach style { style-1 style-2 } {
+ gdb_test "help show style $style" \
+ [gen_help_show_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+
+
+ set out [gen_help_set_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+
+ gdb_test "help set style $style" \
+ [gen_help_set_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+ }
+
+ gdb_test "help show style style-3" \
+ [gen_help_show_style_re "style-3" true \
+ [multi_line \
+ "Style-3 display styling\\." \
+ "This is a multi-line documentation" \
+ "string describing style-3\\."]]
+
+ gdb_test "help show style style-4" \
+ [gen_help_show_style_re "style-4" false \
+ [multi_line \
+ "The style-4 display styling\\." \
+ "Configure style-4 colors and display intensity\\."]]
+
+ for { set i 1 } { $i < 5 } { incr i } {
+ gdb_test "python print(style_$i)" \
+ "<gdb.StyleParameterSet name='style-$i'>" \
+ "print repr of style_$i"
+ }
+
+ # There is no 'style-4 intensity' property.
+ gdb_test "show style style-4 foreground" \
+ "The \"style-4\" style foreground color is: none"
+ gdb_test "show style style-4 background" \
+ "The \"style-4\" style background color is: none"
+ gdb_test "show style style-4 intensity" \
+ "Undefined show style style-4 command: \"intensity\"\\. Try \"help show style style-4\"\\."
+
+ # There is a 'style-1 intensity' property.
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: none" \
+ "show style-1 foreground before changes"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: none" \
+ "show style-1 background before changes"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: normal" \
+ "show style-1 intensity before changes"
+
+ # Grab the gdb.Style objects from 'style-1'.
+ gdb_test_no_output "python s1 = style_1.style"
+ gdb_test_no_output "python s2 = style_1.value"
+
+ # Check both represent the same style.
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=none, bg=none, intensity=normal>" \
+ "print s1 style before changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=none, bg=none, intensity=normal>" \
+ "print s2 style before changes"
+
+ gdb_test_no_output \
+ "python s1.foreground=gdb.Color('red')" "set foreground"
+ gdb_test_no_output \
+ "python s1.background=gdb.Color('blue')" "set background"
+ gdb_test_no_output \
+ "python s1.intensity=gdb.INTENSITY_DIM" "set intensity"
+
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=red, bg=blue, intensity=dim>" \
+ "print s1 style after first set of changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=red, bg=blue, intensity=dim>" \
+ "print s2 style after first set of changes"
+
+ # Check the style properties have updated.
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: red" \
+ "show style-1 foreground after first set of changes"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: blue" \
+ "show style-1 background after first set of changes"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: dim" \
+ "show style-1 intensity after first set of changes"
+
+ # Change the style properties, check gdb.Style objects update.
+ gdb_test_no_output "set style style-1 foreground yellow"
+ gdb_test_no_output "set style style-1 background cyan"
+ gdb_test_no_output "set style style-1 intensity bold"
+
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=yellow, bg=cyan, intensity=bold>" \
+ "print s1 style after second set of changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=yellow, bg=cyan, intensity=bold>" \
+ "print s2 style after second set of changes"
+
+ # Assign a gdb.Style to set 'style-1'. First create some unnamed
+ # style objects that can be used.
+ gdb_test_no_output \
+ "python uns1 = gdb.Style(foreground=gdb.Color('white'), background=gdb.Color('black'), intensity=gdb.INTENSITY_BOLD)" \
+ "create uns1"
+ gdb_test_no_output \
+ "python uns2 = gdb.Style(foreground=gdb.Color('black'), background=gdb.Color('white'), intensity=gdb.INTENSITY_DIM)" \
+ "create uns2"
+
+ gdb_test_no_output "python style_1.value = uns1"
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: white" \
+ "show style-1 foreground after assigning uns1"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: black" \
+ "show style-1 background after assigning uns1"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: bold" \
+ "show style-1 intensity after assigning uns1"
+
+ gdb_test_no_output "python style_1.style = uns2"
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: black" \
+ "show style-1 foreground after assigning uns2"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: white" \
+ "show style-1 background after assigning uns2"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: dim" \
+ "show style-1 intensity after assigning uns2"
+
+ # Assign a style with an intensity that is not 'NORMAL' to a
+ # StyleParameterSet that doesn't have an intensity. The new
+ # intensity setting should be ignored.
+ gdb_test_no_output "python style_4.style = uns1"
+ gdb_test "show style style-4 foreground" \
+ "The \"style-4\" style foreground color is: white" \
+ "show style-4 foreground after assigning uns1"
+ gdb_test "show style style-4 background" \
+ "The \"style-4\" style background color is: black" \
+ "show style-4 background after assigning uns1"
+ gdb_test "show style style-4 intensity" \
+ "Undefined show style style-4 command: \"intensity\"\\. Try \"help show style style-4\"\\." \
+ "show style-4 intensity after assigning uns1"
+
+ gdb_test "python print(style_4.style)" \
+ "<gdb.Style name='style-4', fg=white, bg=black, intensity=normal>" \
+ "print string repr of style_4's style"
+}
+
+# Test creating a style prefix with gdb.ParameterPrefix, then adding
+# some styles within the new prefix. Change the style through the CLI
+# and confirm that the associated Python object updated as expected.
+proc_with_prefix test_style_prefix {} {
+ gdb_test_no_output \
+ "python gdb.ParameterPrefix(\"style my-style-group\", gdb.COMMAND_NONE)"
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"my-style-group style-1\")" \
+ "create style-1"
+ gdb_test_no_output \
+ "python style_2 = gdb.StyleParameterSet(\"my-style-group style-2\")" \
+ "create style-2"
+
+ gdb_test "python print(style_1.style)" \
+ "<gdb.Style name='my-style-group style-1', fg=none, bg=none, intensity=normal>" \
+ "print 'my-style-group style-1' style before changes"
+ gdb_test "python print(style_2.style)" \
+ "<gdb.Style name='my-style-group style-2', fg=none, bg=none, intensity=normal>" \
+ "print 'my-style-group style-2' style before changes"
+
+ gdb_test_no_output "set style my-style-group style-1 foreground red"
+ gdb_test_no_output "set style my-style-group style-1 background yellow"
+ gdb_test_no_output "set style my-style-group style-1 intensity bold"
+ gdb_test_no_output "set style my-style-group style-2 foreground black"
+ gdb_test_no_output "set style my-style-group style-2 background blue"
+ gdb_test_no_output "set style my-style-group style-2 intensity dim"
+
+ gdb_test "python print(style_1.style)" \
+ "<gdb.Style name='my-style-group style-1', fg=red, bg=yellow, intensity=bold>" \
+ "print 'my-style-group style-1' style after changes"
+ gdb_test "python print(style_2.style)" \
+ "<gdb.Style name='my-style-group style-2', fg=black, bg=blue, intensity=dim>" \
+ "print 'my-style-group style-2' style after changes"
+}
+
+# Test that gdb.StyleParameterSet.apply() works as expected.
+proc_with_prefix test_applying {} {
+ # Create a new StyleParameterSet, and adjust its settings.
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"style-1\")" \
+ "create style-1"
+ gdb_test_no_output \
+ "python uns1 = gdb.Style(foreground=gdb.Color('red'), background=gdb.Color('blue'), intensity=gdb.INTENSITY_BOLD)" \
+ "create uns1"
+ gdb_test_no_output "python style_1 = uns1"
+
+ # When styling is off (which it currently is), no escape sequences
+ # should be added.
+ gdb_test \
+ "python print(style_1.apply('xxx'))" "^xxx" \
+ "apply StyleParameterSet to a string when styling is off"
+
+ # When styling is on, we should see an escape sequence added.
+ gdb_test "with style enabled on -- python print(style_1.apply('xxx'))" \
+ "\033\\\[31;44;1;23;24;27mxxx\033\\\[m" \
+ "apply a style when styling is on"
+}
+
+# Start GDB.
+with_ansi_styling_terminal {
+ clean_restart
+}
+
+# Turn styling off so that the output of 'show style ...' isn't styled, this
+# makes it easier to match the output.
+gdb_test_no_output "set style enabled off"
+
+# Run the tests.
+test_basic_usage
+test_style_prefix
+test_applying
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv8 3/3] gdb/python: extend gdb.write to support styled output
2025-08-27 11:34 ` [PATCHv8 0/3] new gdb.Style API Andrew Burgess
2025-08-27 11:34 ` [PATCHv8 1/3] gdb/python: add gdb.Style class Andrew Burgess
2025-08-27 11:34 ` [PATCHv8 2/3] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
@ 2025-08-27 11:34 ` Andrew Burgess
2025-09-16 16:56 ` Tom Tromey
2025-09-23 16:54 ` [PATCHv9 0/3] new gdb.Style API Andrew Burgess
3 siblings, 1 reply; 61+ messages in thread
From: Andrew Burgess @ 2025-08-27 11:34 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess, Eli Zaretskii
It is already possible to produce styled output from Python by
converting the gdb.Style to its escape code sequence, and writing that
to the output stream.
But this commit adds an alternative option to the mix by extending the
existing gdb.write() function to accept a 'style' argument. The value
of this argument can be 'None' to indicate no style change should be
performed, this is the default, and matches the existing behaviour.
Or the new 'style' argument can be a gdb.Style object, in which case
the specified style is applied only for the string passed to
gdb.write, after which the default style is re-applied.
Using gdb.write with a style object more closely matches how GDB
handles styling internally, and has the benefit that the user doesn't
need to remember to restore the default style when they are done.
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
---
gdb/NEWS | 3 +
gdb/doc/python.texi | 9 ++-
gdb/python/py-style.c | 27 +++++++++
gdb/python/python-internal.h | 15 +++++
gdb/python/python.c | 30 ++++++++--
| 56 +++++++++++++++++++
| 16 ++++++
gdb/testsuite/gdb.python/py-style.exp | 29 ++++++++++
8 files changed, 179 insertions(+), 6 deletions(-)
diff --git a/gdb/NEWS b/gdb/NEWS
index 6539c917652..14b825226d0 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -148,6 +148,9 @@ info threads [-gid] [-stopped] [-running] [ID]...
Use gdb.StyleParameterSet(NAME) to create 'set style NAME ...'
and 'show style NAME ...' parameters.
+ ** The gdb.write() function now takes an additional, optional,
+ 'style' argument, which can be used to style the output.
+
** 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 cc033d1bccd..9bd090971a6 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -452,7 +452,7 @@ Basic Python
historical compatibility.
@end defun
-@defun gdb.write (string @r{[}, stream@r{]})
+@defun gdb.write (string @r{[}, stream@r{]} @r{[}, style@r{]})
Print a string to @value{GDBN}'s paginated output stream. The
optional @var{stream} determines the stream to print to. The default
stream is @value{GDBN}'s standard output stream. Possible stream
@@ -475,6 +475,13 @@ Basic Python
@value{GDBN}'s log stream (@pxref{Logging Output}).
@end table
+The @var{style} should be a @code{gdb.Style} object (@pxref{Styles In
+Python}), or @code{None} (the default). If @var{style} is @code{None}
+then the current style for @var{stream} will be applied to @var{text}.
+If @var{style} is a @code{gdb.Style} object, then this style is
+applied to @var{text}, after which the default output style is
+restored.
+
Writing to @code{sys.stdout} or @code{sys.stderr} will automatically
call this function and will automatically direct the output to the
relevant stream.
diff --git a/gdb/python/py-style.c b/gdb/python/py-style.c
index b3983af3a85..0e9af1fc4c4 100644
--- a/gdb/python/py-style.c
+++ b/gdb/python/py-style.c
@@ -353,6 +353,15 @@ stylepy_init (PyObject *self, PyObject *args, PyObject *kwargs)
\f
+/* See python-internal.h. */
+
+bool
+gdbpy_is_style (PyObject *obj)
+{
+ gdb_assert (obj != nullptr);
+ return PyObject_TypeCheck (obj, &style_object_type);
+}
+
/* Return the ui_file_style for STYLEPY. If the style cannot be found,
then return an empty optional, and set a Python error. */
@@ -369,6 +378,24 @@ stylepy_to_style (style_object *stylepy)
return style;
}
+/* See python-internal.h. */
+
+std::optional<ui_file_style>
+gdbpy_style_object_to_ui_file_style (PyObject *obj)
+{
+ gdb_assert (obj != nullptr);
+ if (!gdbpy_is_style (obj))
+ {
+ PyErr_Format
+ (PyExc_TypeError, _("argument must be a gdb.Style object, not %s."),
+ Py_TYPE (obj)->tp_name);
+ return {};
+ }
+
+ style_object *style_obj = (style_object *) obj;
+ return stylepy_to_style (style_obj);
+}
+
/* 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
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index f61a1753ac4..e11b9f92b52 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -570,6 +570,21 @@ struct symtab_and_line *sal_object_to_symtab_and_line (PyObject *obj);
frame_info_ptr frame_object_to_frame_info (PyObject *frame_obj);
struct gdbarch *arch_object_to_gdbarch (PyObject *obj);
+/* Return true if OBJ is a gdb.Style object. OBJ must not be NULL. */
+
+extern bool gdbpy_is_style (PyObject *obj);
+
+/* Return the ui_file_style from OBJ, a gdb.Style object. OBJ must not be
+ NULL, but can be a non gdb.Style object, in which case a Python error is
+ set and an empty optional is returned.
+
+ It is also possible that OBJ is a gdb.Style object, but the underlying
+ style cannot be fetched for some reason. If this happens then a Python
+ error is set and an empty optional is returned. */
+
+extern std::optional<ui_file_style>
+ gdbpy_style_object_to_ui_file_style (PyObject *obj);
+
extern PyObject *gdbpy_execute_mi_command (PyObject *self, PyObject *args,
PyObject *kw);
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 1af7896eb08..77d96da61c6 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -1561,12 +1561,22 @@ static PyObject *
gdbpy_write (PyObject *self, PyObject *args, PyObject *kw)
{
const char *arg;
- static const char *keywords[] = { "text", "stream", NULL };
+ static const char *keywords[] = { "text", "stream", "style", nullptr };
int stream_type = 0;
+ PyObject *style_obj = Py_None;
- if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s|i", keywords, &arg,
- &stream_type))
- return NULL;
+ if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s|iO", keywords, &arg,
+ &stream_type, &style_obj))
+ return nullptr;
+
+ if (style_obj != Py_None && !gdbpy_is_style (style_obj))
+ {
+ PyErr_Format
+ (PyExc_TypeError,
+ _("'style' argument must be gdb.Style or None, not %s."),
+ Py_TYPE (style_obj)->tp_name);
+ return nullptr;
+ }
try
{
@@ -1584,7 +1594,17 @@ gdbpy_write (PyObject *self, PyObject *args, PyObject *kw)
break;
}
- gdb_puts (arg, stream);
+ if (style_obj == Py_None)
+ gdb_puts (arg, stream);
+ else
+ {
+ std::optional<ui_file_style> style
+ = gdbpy_style_object_to_ui_file_style (style_obj);
+ if (!style.has_value ())
+ return nullptr;
+
+ fputs_styled (arg, style.value (), stream);
+ }
}
catch (const gdb_exception &except)
{
--git a/gdb/testsuite/gdb.python/py-color-pagination.exp b/gdb/testsuite/gdb.python/py-color-pagination.exp
index 1974dbb4186..6b10f9ca793 100644
--- a/gdb/testsuite/gdb.python/py-color-pagination.exp
+++ b/gdb/testsuite/gdb.python/py-color-pagination.exp
@@ -131,8 +131,64 @@ proc test_pagination { type mode } {
}
}
+# Run the command 'style-fill-v2' which fills the screen with output and
+# triggers the pagination prompt. Check that styling is applied correctly
+# to the output. This v2 command is exercising passing a style to
+# gdb.write() rather than passing the escape sequence for the style.
+proc test_pagination_v2 { } {
+ set saw_bad_color_handling false
+ set expected_restore_color ""
+ set last_color ""
+ gdb_test_multiple "style-fill-v2" "" {
+ -re "^style-fill-v2\r\n" {
+ exp_continue
+ }
+
+ -re "^(${::any_color}\033\\\[m)(${::any_color})$::str\033\\\[m" {
+ # After a continuation prompt GDB will restore the previous
+ # color, and then we immediately switch to a new color.
+ set restored_color $expect_out(1,string)
+ if { $restored_color ne $expected_restore_color } {
+ set saw_bad_color_handling true
+ }
+ set last_color $expect_out(2,string)
+ exp_continue
+ }
+
+ -re "^(${::any_color})$::str\033\\\[m" {
+ # This pattern matches printing STR in all cases that are not
+ # immediately after a pagination prompt. In this case there is
+ # a single escape sequence to set the color.
+ set last_color $expect_out(1,string)
+ exp_continue
+ }
+
+ -re "^$::pagination_prompt$" {
+ # After a pagination prompt we expect GDB to restore the last
+ # color, but this will then be disabled due to a styled
+ # gdb.write emitting a return to default style escape sequence.
+ set expected_restore_color "$last_color\033\[m"
+
+ # Send '\n' to view more output.
+ send_gdb "\n"
+ exp_continue
+ }
+
+ -re "^\r\n" {
+ # The matches the newline sent to the continuation prompt.
+ exp_continue
+ }
+
+ -re "^$::gdb_prompt $" {
+ gdb_assert { !$saw_bad_color_handling } $gdb_test_name
+ }
+ }
+}
+
foreach_with_prefix type { color style } {
foreach_with_prefix mode { write print } {
test_pagination $type $mode
}
}
+
+test_pagination_v2
--git a/gdb/testsuite/gdb.python/py-color-pagination.py b/gdb/testsuite/gdb.python/py-color-pagination.py
index f0252e5bf86..9cdc76cb0ce 100644
--- a/gdb/testsuite/gdb.python/py-color-pagination.py
+++ b/gdb/testsuite/gdb.python/py-color-pagination.py
@@ -62,5 +62,21 @@ class StyleTester(gdb.Command):
write(mode, "\n")
+class StyleTester2(gdb.Command):
+ def __init__(self):
+ super().__init__("style-fill-v2", gdb.COMMAND_USER)
+
+ def invoke(self, args, from_tty):
+ str = "<" + "-" * 78 + ">"
+ for i in range(0, 20):
+ for color_name in basic_colors:
+ c = gdb.Color(color_name)
+ s = gdb.Style(foreground=c)
+ gdb.write(str, style=s)
+
+ gdb.write("\n")
+
+
ColorTester()
StyleTester()
+StyleTester2()
diff --git a/gdb/testsuite/gdb.python/py-style.exp b/gdb/testsuite/gdb.python/py-style.exp
index b2efe9a97e3..491e189774e 100644
--- a/gdb/testsuite/gdb.python/py-style.exp
+++ b/gdb/testsuite/gdb.python/py-style.exp
@@ -340,3 +340,32 @@ gdb_test_no_output "python input_text = \"a totally different string that is als
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"
+
+# Test gdb.write passing in a style. Define a helper function to
+# ensure all output is flushed before we return to the prompt.
+gdb_test_multiline "create function to call gdb.write then flush" \
+ "python" "" \
+ "def write_and_flush(*args, **kwargs):" "" \
+ " gdb.write(*args, **kwargs)" "" \
+ " gdb.write(\"\\n\")" "" \
+ " gdb.flush(gdb.STDOUT)" "" \
+ "end" ""
+
+gdb_test "python write_and_flush(\"some text\")" \
+ "^some text" "unstyled text, no style passed"
+
+gdb_test "python write_and_flush(\"some text\", style=None)" \
+ "^some text" "unstyled text, pass style as None"
+
+gdb_test "python write_and_flush(\"some text\", style=filename_style)" \
+ "^\033\\\[34;41;2;23;24;27msome text\033\\\[m" \
+ "styled output, pass style by keyword"
+
+gdb_test "python write_and_flush(\"some text\", gdb.STDOUT, filename_style)" \
+ "^\033\\\[34;41;2;23;24;27msome text\033\\\[m" \
+ "styled output, pass style by position"
+
+gdb_test "python write_and_flush(\"some text\", style='filename')" \
+ [multi_line \
+ "Python Exception <class 'TypeError'>: 'style' argument must be gdb\\.Style or None, not str\\." \
+ "Error occurred in Python: 'style' argument must be gdb\\.Style or None, not str\\."]
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* Re: [PATCHv8 1/3] gdb/python: add gdb.Style class
2025-08-27 11:34 ` [PATCHv8 1/3] gdb/python: add gdb.Style class Andrew Burgess
@ 2025-09-16 16:47 ` Tom Tromey
2025-09-23 16:59 ` Andrew Burgess
0 siblings, 1 reply; 61+ messages in thread
From: Tom Tromey @ 2025-09-16 16:47 UTC (permalink / raw)
To: Andrew Burgess; +Cc: gdb-patches, Eli Zaretskii
>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:
Andrew> +static std::optional<ui_file_style>
Andrew> +stylepy_style_from_name (const char *name, bool *has_intensity_ptr = nullptr,
Andrew> + const cmd_list_element **found_cmd_ptr = nullptr)
Andrew> +{
Andrew> + std::string cmd_str = std::string ("show style ") + name;
Andrew> +
Andrew> + struct cmd_list_element *cmd = nullptr;
Andrew> + struct cmd_list_element *alias = nullptr;
Andrew> + int found;
Andrew> + try
Andrew> + {
Andrew> + struct cmd_list_element *prefix;
Andrew> + found = lookup_cmd_composition (cmd_str.c_str (), &alias, &prefix, &cmd);
Andrew> + }
Andrew> + catch (const gdb_exception &ex)
Andrew> + {
Andrew> + PyErr_Format (PyExc_RuntimeError,
Andrew> + _("style '%s' cannot be found."), name);
Andrew> + return {};
Noting this for a later comment...
Andrew> +static PyObject *
Andrew> +stylepy_apply (PyObject *self, PyObject *args, PyObject *kw)
Andrew> +{
Andrew> + style_object *style_obj = (style_object *) self;
Andrew> +
Andrew> + static const char *keywords[] = { "string", nullptr };
Andrew> + PyObject *input_obj;
Andrew> +
Andrew> + if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "O!", keywords,
Andrew> + &PyUnicode_Type, &input_obj))
Andrew> + return nullptr;
Why "O!" and not "s" here?
Andrew> + gdb::unique_xmalloc_ptr<char>
Andrew> + input (python_string_to_target_string (input_obj));
target string seems like an unusual choice here.
Andrew> + std::string output
Andrew> + = string_printf ("%s%s%s", style->to_ansi ().c_str (), input.get (),
Andrew> + ui_file_style ().to_ansi ().c_str ());
This can just using "+" which seems a little cleaner.
Andrew> + /* Handle named styles. This is harder, we need to write to the actual
Andrew> + parameter. First though, look up the named style to see if it has an
Andrew> + intensity. HAS_INTENSITY will be set true only if the style exists,
Andrew> + and has an 'intensity' setting. */
Andrew> +
Andrew> + bool has_intensity = false;
Andrew> + (void) stylepy_style_from_name (style_obj->style_name, &has_intensity);
Andrew> + if (!has_intensity)
Andrew> + {
Andrew> + PyErr_Format
Andrew> + (PyExc_ValueError, "the intensity of style '%s' is not writable.",
Andrew> + style_obj->style_name);
Andrew> + return -1;
I guess I don't know the Python rule here, but it seems like if
stylepy_style_from_name returns {}, then this can set an exception while
one is already set. See the snippet above.
Instead of the (void) I think it would be a bit better to just check the
return result of stylepy_style_from_name, and if it returns an error,
just return -1 directly; and only then do the has_intensity check.
Tom
^ permalink raw reply [flat|nested] 61+ messages in thread
* Re: [PATCHv8 2/3] gdb/python: new class gdb.StyleParameterSet
2025-08-27 11:34 ` [PATCHv8 2/3] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
@ 2025-09-16 16:50 ` Tom Tromey
0 siblings, 0 replies; 61+ messages in thread
From: Tom Tromey @ 2025-09-16 16:50 UTC (permalink / raw)
To: Andrew Burgess; +Cc: gdb-patches, Eli Zaretskii
>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:
Andrew> + If styling is globally disabled ('set style enabled off') then no escape sequences
Andrew> + will be addedded, the input string is returned."""
Typo in "added"
Otherwise this looks good to me.
Approved-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 61+ messages in thread
* Re: [PATCHv8 3/3] gdb/python: extend gdb.write to support styled output
2025-08-27 11:34 ` [PATCHv8 3/3] gdb/python: extend gdb.write to support styled output Andrew Burgess
@ 2025-09-16 16:56 ` Tom Tromey
2025-09-23 17:05 ` Andrew Burgess
0 siblings, 1 reply; 61+ messages in thread
From: Tom Tromey @ 2025-09-16 16:56 UTC (permalink / raw)
To: Andrew Burgess; +Cc: gdb-patches, Eli Zaretskii
>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:
Andrew> Using gdb.write with a style object more closely matches how GDB
Andrew> handles styling internally, and has the benefit that the user doesn't
Andrew> need to remember to restore the default style when they are done.
I'm mildly meh on this one since I tend to think users should use print
and that gdb.write is an implementation detail. But since it exists...
Andrew> + return PyObject_TypeCheck (obj, &style_object_type);
Andrew> +/* See python-internal.h. */
Andrew> +
Andrew> +std::optional<ui_file_style>
Andrew> +gdbpy_style_object_to_ui_file_style (PyObject *obj)
Andrew> +{
Andrew> + gdb_assert (obj != nullptr);
Andrew> + if (!gdbpy_is_style (obj))
Andrew> + {
Andrew> + PyErr_Format
Andrew> + (PyExc_TypeError, _("argument must be a gdb.Style object, not %s."),
Andrew> + Py_TYPE (obj)->tp_name);
Andrew> + return {};
Andrew> + }
Andrew> +
Andrew> + style_object *style_obj = (style_object *) obj;
Andrew> + return stylepy_to_style (style_obj);
Andrew> + if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s|iO", keywords, &arg,
Andrew> + &stream_type, &style_obj))
Andrew> + return nullptr;
Andrew> +
Andrew> + if (style_obj != Py_None && !gdbpy_is_style (style_obj))
Andrew> + {
Andrew> + PyErr_Format
Andrew> + (PyExc_TypeError,
Andrew> + _("'style' argument must be gdb.Style or None, not %s."),
Andrew> + Py_TYPE (style_obj)->tp_name);
Andrew> + return nullptr;
FWIW if the type object is available there's a way to get Python to do
this bit when parsing the arguments.
This explicit check is fine too, but it seems to me that this is
redundant with the check that gdbpy_style_object_to_ui_file_style does
and so this could just delegate to that.
Tom
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv9 0/3] new gdb.Style API
2025-08-27 11:34 ` [PATCHv8 0/3] new gdb.Style API Andrew Burgess
` (2 preceding siblings ...)
2025-08-27 11:34 ` [PATCHv8 3/3] gdb/python: extend gdb.write to support styled output Andrew Burgess
@ 2025-09-23 16:54 ` Andrew Burgess
2025-09-23 16:54 ` [PATCHv9 1/3] gdb/python: add gdb.Style class Andrew Burgess
` (3 more replies)
3 siblings, 4 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-09-23 16:54 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
After the gdb.Color support was added to the Python API I wondered if
this would be enough to easily add styling to custom Python commands.
But I don't think it is, there's still lots that would need to be
handled in user code, for example, the intensity setting. And having
styles that track named styles (e.g. if I want to apply the 'filename'
style to some text from Python).
So this series builds on top of the gdb.Color API to provide a new
gdb.Style API which, I hope, makes it easy to start writing Python
commands that can use GDB's styles.
Here's an example of a simple custome command that uses styles:
import os
class user_home_cmd(gdb.Command):
def __init__(self):
super().__init__("user-home", gdb.COMMAND_USER)
self._style = gdb.Style('filename')
def invoke(self, args, from_tty):
print("The filename is %s."
% (self._style.apply(os.environ['HOME'])))
user_home_cmd()
The new 'user-home' command prints the current users home directory
using 'filename' style. Changing the 'filename' style and re-running
the command will track the style changes.
In v2:
- Fixed an issue with the py-style-parameter-set.exp test; GDB was
not started in a terminal with styling support in one case, so
tests would fail depending on how the testsuite was being run.
In v3:
- Another test issue. This time, the error message given when
parsing an argument of the wrong type changes with Python version.
Relax the regexp to accept any 'TypeError' message.
In v4:
- Two new patches. First patch extends pager_file so that
gdb.Color, and gdb.Style when it's added can work well with GDB's
pager. Last patch extends the gdb.write() Python function to also
support styling.
- I've renamed some of the classes in python/py-style.c, nothing
major, I replaced 'stylepy' with 'style' in some names. I think
the new names are clearer, and better match the rest of GDB's
Python API code.
- Unfortunately, the new pager_file changes mean that this patch
series now depends on this other patch:
https://inbox.sourceware.org/gdb-patches/444008aeae2bb3c68cf868fa317374b3d7973860.1750197766.git.aburgess@redhat.com
Without that patch there will be some failures in
gdb.python/py-color-pagination.exp. I'm not proposing to merge
this until that other patch, or something like it, is merged. But
this series could be reviewed independently.
In v5:
- The pager_file styling fixes mentioned for v4 have now been merged
to master. I've rebased this series on top of them, and all the
tests are now passing.
- I've tweaked the wording for a couple of the commit messages just
to make things clearer.
- No real code changes.
In v6:
- Rebased as there were some style related changes merged to master
recently. No conflicts, and everything still works fine, so there
are no real changes here.
In v7:
- Rebased as there were more style related changes to master which
caused some merged conflicts, and test failures. Both turned out
to be trivial to fix.
In v8:
- I merged the gdb.Color + pagination fix, this leaves the 3 patches
related to the gdb.Style API.
- I've updated the tests in patch 3 to take commit e17e65798e4 into
account.
- No updates needed to the actual GDB changes.
In v9:
- Patch 1 updated inline with Tom's comments. In stylepy_apply,
added comment to explain use of O!, switched to use
python_string_to_host_string, and use "+" to link std::strings
together. In stylepy_set_intensity I added an early return if the
stylepy_style_from_name fails.
- Patch 2 is unchanged.
- In patch 3, I removed some of the error checking from
gdbpy_style_object_to_ui_file_style, and replaced it with an
assert. This allows the error checking, along with a better error
message, to remain in gdbpy_write.
- Rebased to HEAD and retested.
Thanks,
Andrew
---
Andrew Burgess (3):
gdb/python: add gdb.Style class
gdb/python: new class gdb.StyleParameterSet
gdb/python: extend gdb.write to support styled output
gdb/Makefile.in | 1 +
gdb/NEWS | 14 +
gdb/doc/python.texi | 262 +++++-
gdb/python/lib/gdb/__init__.py | 209 +++++
gdb/python/py-style.c | 822 ++++++++++++++++++
gdb/python/python-internal.h | 14 +
gdb/python/python.c | 30 +-
.../gdb.python/py-color-pagination.exp | 61 +-
.../gdb.python/py-color-pagination.py | 36 +
.../gdb.python/py-style-parameter-set.exp | 366 ++++++++
gdb/testsuite/gdb.python/py-style.exp | 371 ++++++++
gdb/ui-style.h | 6 +
12 files changed, 2184 insertions(+), 8 deletions(-)
create mode 100644 gdb/python/py-style.c
create mode 100644 gdb/testsuite/gdb.python/py-style-parameter-set.exp
create mode 100644 gdb/testsuite/gdb.python/py-style.exp
base-commit: 3dfb60bdd5c6affab91a23172352a027ac05768b
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv9 1/3] gdb/python: add gdb.Style class
2025-09-23 16:54 ` [PATCHv9 0/3] new gdb.Style API Andrew Burgess
@ 2025-09-23 16:54 ` Andrew Burgess
2025-09-23 16:54 ` [PATCHv9 2/3] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
` (2 subsequent siblings)
3 siblings, 0 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-09-23 16:54 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess, Eli Zaretskii
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 | 801 ++++++++++++++++++
| 5 +-
| 20 +
gdb/testsuite/gdb.python/py-style.exp | 342 ++++++++
gdb/ui-style.h | 6 +
8 files changed, 1320 insertions(+), 2 deletions(-)
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 beacefcfdd5..80d5aef45ff 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 8be367d2424..8db38826f74 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -179,6 +179,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 3763eee9d63..0795e66765c 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.
@@ -7281,6 +7282,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..51b35f2fa7e
--- /dev/null
+++ b/gdb/python/py-style.c
@@ -0,0 +1,801 @@
+/* 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);
--git a/gdb/testsuite/gdb.python/py-color-pagination.exp b/gdb/testsuite/gdb.python/py-color-pagination.exp
index e7a9e4fec5f..1974dbb4186 100644
--- a/gdb/testsuite/gdb.python/py-color-pagination.exp
+++ b/gdb/testsuite/gdb.python/py-color-pagination.exp
@@ -14,7 +14,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# This file is part of the GDB testsuite. It tests gdb.Color and how this
-# interacts with GDB's pagination system.
+# interacts with GDB's pagination system. The test also tests gdb.Style
+# because the tests are very similar.
load_lib gdb-python.exp
@@ -130,7 +131,7 @@ proc test_pagination { type mode } {
}
}
-foreach_with_prefix type { color } {
+foreach_with_prefix type { color style } {
foreach_with_prefix mode { write print } {
test_pagination $type $mode
}
--git a/gdb/testsuite/gdb.python/py-color-pagination.py b/gdb/testsuite/gdb.python/py-color-pagination.py
index efd501eedf5..f0252e5bf86 100644
--- a/gdb/testsuite/gdb.python/py-color-pagination.py
+++ b/gdb/testsuite/gdb.python/py-color-pagination.py
@@ -43,4 +43,24 @@ class ColorTester(gdb.Command):
write(mode, "\n")
+class StyleTester(gdb.Command):
+ def __init__(self):
+ super().__init__("style-fill", gdb.COMMAND_USER)
+
+ def invoke(self, args, from_tty):
+ mode = args
+ str = "<" + "-" * 78 + ">"
+ for i in range(0, 20):
+ for color_name in basic_colors:
+ c = gdb.Color(color_name)
+ s = gdb.Style(foreground=c)
+ write(mode, s.escape_sequence())
+ write(mode, str)
+
+ default = gdb.Style()
+ write(mode, default.escape_sequence())
+ write(mode, "\n")
+
+
ColorTester()
+StyleTester()
diff --git a/gdb/testsuite/gdb.python/py-style.exp b/gdb/testsuite/gdb.python/py-style.exp
new file mode 100644
index 00000000000..b2efe9a97e3
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-style.exp
@@ -0,0 +1,342 @@
+# 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"
diff --git a/gdb/ui-style.h b/gdb/ui-style.h
index 1ea3556869d..64f710b0cc1 100644
--- a/gdb/ui-style.h
+++ b/gdb/ui-style.h
@@ -334,6 +334,12 @@ struct ui_file_style
return m_intensity;
}
+ /* Set the intensity of this style. */
+ void set_intensity (intensity i)
+ {
+ m_intensity = i;
+ }
+
/* Return true if this style specified italic display; false
otherwise. */
bool is_italic () const
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv9 2/3] gdb/python: new class gdb.StyleParameterSet
2025-09-23 16:54 ` [PATCHv9 0/3] new gdb.Style API Andrew Burgess
2025-09-23 16:54 ` [PATCHv9 1/3] gdb/python: add gdb.Style class Andrew Burgess
@ 2025-09-23 16:54 ` Andrew Burgess
2025-09-23 16:54 ` [PATCHv9 3/3] gdb/python: extend gdb.write to support styled output Andrew Burgess
2025-10-03 19:27 ` [PATCHv9 0/3] new gdb.Style API Tom Tromey
3 siblings, 0 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-09-23 16:54 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess, Eli Zaretskii, Tom Tromey
Add a new helper class gdb.StyleParameterSet. This new class can be
used to simplify creation of new style parameter sets. A style
parameter set is the 'foreground', 'background', and (optionally), the
'intensity' settings, all grouped under a single prefix command.
And example usage is:
(gdb) python s = gdb.StyleParameterSet("my-style")
(gdb) show style my-style
style my-style background: The "my-style" style background color is: none
style my-style foreground: The "my-style" style foreground color is: none
style my-style intensity: The "my-style" style display intensity is: normal
(gdb)
Having created a gdb.StyleParameterSet, the object itself can be used
to access a named style corresponding to the setting group, like this:
(gdb) python print(s.style)
<gdb.Style name='my-style', fg=none, bg=none, intensity=normal>
(gdb)
Of course, having access to the gdb.Style makes it easy to change the
settings, or the settings can be adjusted via the normal CLI 'set'
commands.
As gdb.StyleParameterSet manages a set of parameters, and the
gdb.Parameter class uses Parameter.value as the attribute to read the
parameter's value, there is also StyleParameterSet.value, but this is
just an alias for StyleParameterSet.style, that is, it allows the
gdb.Style object to be read and written too.
It is worth noting that this class only creates a single level of
prefix command. As an example GDB has style 'disassembler mnemonic',
where the 'disassembler' part is a group of related styles. If a user
wanted to create:
style
my-style-group
style-1
style-2
style-3
Where each of 'style-1', 'style-2', and 'style-3' will have the full
set of 'foreground', 'background', and 'intensity', then the
gdb.StyleParameterSet can be used to create the 'style-N' part, but
the user will have to create the 'my-style-group' prefix themselves,
possibly using gdb.ParameterPrefix, e.g.:
gdb.ParameterPrefix("style my-style-group", gdb.COMMAND_NONE)
gdb.StyleParameterSet("my-style-group style-1")
gdb.StyleParameterSet("my-style-group style-2")
gdb.StyleParameterSet("my-style-group style-3")
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
Approved-By: Tom Tromey <tom@tromey.com>
---
gdb/NEWS | 4 +
gdb/doc/python.texi | 115 +++++-
gdb/python/lib/gdb/__init__.py | 209 ++++++++++
.../gdb.python/py-style-parameter-set.exp | 366 ++++++++++++++++++
4 files changed, 693 insertions(+), 1 deletion(-)
create mode 100644 gdb/testsuite/gdb.python/py-style-parameter-set.exp
diff --git a/gdb/NEWS b/gdb/NEWS
index 8db38826f74..6d83083f600 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -186,6 +186,10 @@ info threads [-gid] [-stopped] [-running] [ID]...
gdb.INTENSITY_DIM for use with gdb.Style when representing
intensities.
+ ** New gdb.StyleParameterSet for creating custom style settings.
+ Use gdb.StyleParameterSet(NAME) to create 'set style NAME ...'
+ and 'show style NAME ...' parameters.
+
** 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 0795e66765c..45e53845c6e 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -5312,6 +5312,7 @@ Parameters In Python
feature-2}, then the @kbd{plugin-name} would need to be a prefix
command (@pxref{CLI Commands In Python}).
+@anchor{gdb.ParameterPrefix}
However, when creating parameters, you will almost always need to
create two prefix commands, one as a @kbd{set} sub-command, and one as
a @kbd{show} sub-command. @value{GDBN} provides the
@@ -5398,6 +5399,117 @@ Parameters In Python
ExampleParam("plugin-name feature-2")
@end smallexample
+@anchor{Creating Style Parameters}
+The helper class @code{gdb.StyleParameterSet} exists to make it easier
+to create new styles. @value{GDBN} places style settings under
+@samp{show style @dots{}} and @samp{set style @dots{}}, an example of a style
+is @samp{filename}. Each style is really a prefix command (@pxref{CLI
+Commands In Python}), with sub-commands @samp{foreground},
+@samp{background}, and optionally, @samp{intensity}.
+
+It is simple enough to create a new style using two @code{gdb.Command}
+objects for the prefix commands (one for @samp{set}, and one for
+@samp{show}), and three @code{gdb.Parameter} objects, one each for the
+@samp{foreground}, @samp{background}, and @samp{intensity}. You would
+also want to take care to craft the help text so that the new style
+behaves the same as the existing styles.
+
+Or, you can use the @code{gdb.StyleParameterSet} class, which takes
+care of all this, as the following example shows:
+
+@smallexample
+@group
+(@value{GDBP}) python s = gdb.StyleParameterSet("my-style")
+(@value{GDBP}) show style my-style
+style my-style background: The "my-style" style background color is: none
+style my-style foreground: The "my-style" style foreground color is: none
+style my-style intensity: The "my-style" style display intensity is: normal
+(@value{GDBP})
+@end group
+@end smallexample
+
+You might also want to group a number of styles within a new prefix,
+similar to how @value{GDBN} groups disassembler related styles within
+the @samp{style disassembler} prefix. This can be done using
+@code{gdb.ParameterPrefix} (@pxref{gdb.ParameterPrefix}), as in this
+example:
+
+@smallexample
+@group
+(@value{GDBP}) python gdb.ParameterPrefix("style group", gdb.COMMAND_NONE)
+(@value{GDBP}) python s_a = gdb.StyleParameterSet("group aa")
+(@value{GDBP}) python s_b = gdb.StyleParameterSet("group bb")
+(@value{GDBP}) show style group
+style group aa background: The "group aa" style background color is: none
+style group aa foreground: The "group aa" style foreground color is: none
+style group aa intensity: The "group aa" style display intensity is: normal
+style group bb background: The "group bb" style background color is: none
+style group bb foreground: The "group bb" style foreground color is: none
+style group bb intensity: The "group bb" style display intensity is: normal
+(@value{GDBP})
+@end group
+@end smallexample
+
+The @code{gdb.StyleParameterSet} class has the following methods and
+attributes:
+
+@defun StyleParameterSet.__init__(name, @w{add_intensity=@code{True}}, @w{doc=@code{None}})
+Create a new style group based on @var{name}, which is a string. For
+example if @var{name} is @samp{my-style}, then @value{GDBN} will
+create the prefix commands @samp{set style my-style} and @samp{show
+style my-style}. Within these prefix commands will be
+@samp{foreground}, @samp{background}, and @samp{intensity} parameters
+with the appropriate types.
+
+It is also possible for @var{name} to consist of multiple words, so
+long as each prefix command (except the last one) already exists. For
+example, it is valid to use a @var{name} value of @samp{disassembler
+my-style}, as the @samp{disassembler} prefix command already exists.
+@value{GDBN} would then create @samp{set style disassembler my-style}
+and @samp{show style disassembler my-style}, and within the
+@samp{my-style} prefixes will be the @samp{foreground},
+@samp{background}, and @samp{intensity} parameters with the
+appropriate types.
+
+Every style requires a @samp{foreground} and @samp{background}, but
+not every style needs an @samp{intensity}. If @var{add_intensity} is
+@code{True} (the default), then the @samp{intensity} parameter will be
+created. If @var{add_intensity} is @code{False}, then the
+@samp{intensity} parameter will not be created.
+
+If the @samp{intensity} parameter is not created, then the
+@code{gdb.Style} (@pxref{Styles In Python}) created from this
+@code{gdb.StyleParameterSet} will have @code{gdb.INTENSITY_NORMAL}.
+
+The @var{doc} should be a string which will be used as the help text
+for the @var{name} prefix command. This text is used as the
+@code{Command.__doc__} value for the @code{gdb.Command} object that is
+the prefix command object (@pxref{CLI Commands In Python}). If
+@var{doc} is @code{None} (the default) then a basic default help text
+is used.
+@end defun
+
+@defun StyleParameterSet.apply(string)
+Equivalent to @code{StyleParameterSet.style.apply(string)}. Returns a
+copy of @var{string} with escape sequences added to the start and end.
+The escape sequence at the start applies this style, and the escape
+sequence at the end restores the terminal default.
+
+If styling is disabled (i.e.@: @samp{set style enabled off}), then no
+escape sequences are added and this method returns a copy of
+@var{string}.
+@end defun
+
+@defvar StyleParameterSet.style
+This read/write attribute holds a @code{gdb.Style} object
+(@pxref{Styles In Python}), that is a named style associated with this
+style parameter group.
+@end defvar
+
+@defvar StyleParameterSet.value
+This is an alias for @code{StyleParameterSet.style}, see above.
+@end defvar
+
@node Functions In Python
@subsubsection Writing new convenience functions
@@ -7294,7 +7406,8 @@ Styles In Python
@value{GDBN} has many styles builtin (@pxref{Output Styling}), and
style objects can be created that apply these builtin styles to Python
-output.
+output. It is also possible to create new styles which can be used to
+style Python output (@pxref{Creating Style Parameters}).
The style class is called @code{gdb.Style}, and has the following
methods and attributes:
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index cedd897ab0f..0388c6a2c00 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -515,3 +515,212 @@ class ParameterPrefix:
def dont_repeat(self):
if self.active_prefix is not None:
self.active_prefix.dont_repeat()
+
+
+class StyleParameterSet:
+ """Create new style parameters.
+
+ A style parameter is a set of parameters that start with 'set style ...'
+ and 'show style ...'. For example 'filename' is a style parameter, and
+ 'disassembler symbol' is another style parameter.
+
+ The name of the style parameter is really a prefix command. Under this
+ we must have two commands 'foreground' and 'background', which are color
+ parameters. A third, optional command 'intensity', is an enum with
+ values 'normal', 'bold', and 'dim'.
+
+ A StyleParameterSet is initialised with a name, e.g. 'filename' or
+ 'disassembler symbol'. The StyleParameterSet creates the prefix
+ commands in the 'set style' and 'show style' name space, and then adds
+ the 'foreground', 'background', and optionally, the 'intensity'
+ commands.
+
+ If you want a whole new style group, similar to 'disassembler',
+ then you need to add this yourself first, then StyleParameterSet
+ can be used to create styles within the new prefix group.
+
+ The 'value' attribute on this object can be used to get and set a
+ gdb.Style object which controls all aspects of this style.
+
+ For readability, the alias 'style' is the same as 'value'.
+ """
+
+ def __init__(self, name, add_intensity=True, doc=None):
+ # The STYLE_NAME is something like 'filename' is 'set style
+ # filename ...', and PARAM_NAME is one of 'foreground',
+ # 'background', or 'intensity'. The DESC_TEXT is the long
+ # form used in help text, like 'foreground color' or 'display
+ # intensity'. The DEFAULT_VALUE is used to set the SELF.value
+ # attribute. And PARAM_TYPE is a gdb.PARAM_* constant. The
+ # ARGS is used for gdb.PARAM_ENUM, which ARGS should be the
+ # enum value list.
+ class style_parameter(Parameter):
+ def __init__(
+ self,
+ style_name,
+ parent_obj,
+ param_name,
+ desc_text,
+ default_value,
+ param_type,
+ *args
+ ):
+ # Setup documentation must be done before calling
+ # parent's __init__ method, as the __init__ reads (and
+ # copies) these values.
+ self.show_doc = "Show the " + desc_text + " for this property."
+ self.set_doc = "Set the " + desc_text + " for this property."
+ self.__doc__ = ""
+
+ # Call the parent's __init__ method to actually create
+ # the parameter.
+ super().__init__(
+ "style " + style_name + " " + param_name,
+ COMMAND_NONE,
+ param_type,
+ *args
+ )
+
+ # Store information we need in other methods.
+ self._style_name = style_name
+ self._desc_text = desc_text
+ self._parent = parent_obj
+
+ # Finally, setup the default value.
+ self.value = default_value
+
+ # Return the 'show style <style-name> <attribute>' string,
+ # which has styling applied.
+ def get_show_string(self, value):
+ s = self._parent.style
+ return (
+ "The "
+ + s.apply('"' + self._style_name + '" style')
+ + " "
+ + self._desc_text
+ + " is: "
+ + value
+ )
+
+ class style_foreground_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "foreground",
+ "foreground color",
+ Color(),
+ PARAM_COLOR,
+ )
+
+ class style_background_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "background",
+ "background color",
+ Color(),
+ PARAM_COLOR,
+ )
+
+ class style_intensity_parameter(style_parameter):
+ def __init__(self, name, parent):
+ super().__init__(
+ name,
+ parent,
+ "intensity",
+ "display intensity",
+ "normal",
+ PARAM_ENUM,
+ ["normal", "bold", "dim"],
+ )
+
+ if doc is None:
+ doc = (
+ "The "
+ + name
+ + " display styling.\nConfigure "
+ + name
+ + " colors and display intensity."
+ )
+
+ ParameterPrefix("style " + name, COMMAND_NONE, doc)
+ self._foreground = style_foreground_parameter(name, self)
+ self._background = style_background_parameter(name, self)
+ if add_intensity:
+ self._intensity = style_intensity_parameter(name, self)
+ self._name = name
+ self._style = None
+
+ @property
+ def value(self):
+ """Return the gdb.Style object for this parameter set."""
+ if self._style is None:
+ self._style = Style(self._name)
+ return self._style
+
+ @property
+ def style(self):
+ """Return the gdb.Style object for this parameter set.
+
+ This is an alias for self.value."""
+ return self.value
+
+ @value.setter
+ def value(self, new_value):
+ """Set this parameter set to NEW_VALUE, a gdb.Style object.
+
+ The attributes of NEW_VALUE are used to update the current settings
+ of this parameter set. If this parameter set was created without
+ an intensity setting, then the intensity of NEW_VALUE is ignored."""
+ if not isinstance(new_value, Style):
+ raise TypeError("value must be gdb.Style, not %s" % type(new_value))
+ self._foreground.value = new_value.foreground
+ self._background.value = new_value.background
+ if hasattr(self, "_intensity"):
+ intensity_value = new_value.intensity
+ if intensity_value == INTENSITY_BOLD:
+ intensity_string = "bold"
+ elif intensity_value == INTENSITY_DIM:
+ intensity_string = "dim"
+ elif intensity_value == INTENSITY_NORMAL:
+ intensity_string = "normal"
+ else:
+ raise ValueError(
+ "unknown intensity value %d from Style" % intensity_value
+ )
+
+ self._intensity.value = intensity_string
+
+ @style.setter
+ def style(self, new_value):
+ """Set this parameter set to NEW_VALUE, a gdb.Style object.
+
+ This is an alias for self.value."""
+ self.value = new_value
+
+ def apply(self, *args, **kwargs):
+ """Apply this style to the arguments.
+
+ Forwards all arguments to self.style.apply(). The arguments should be a string,
+ to which this style is applied. This function returns the same string with
+ escape sequences added to apply this style.
+
+ If styling is globally disabled ('set style enabled off') then no escape sequences
+ will be added, the input string is returned."""
+ return self.style.apply(*args, **kwargs)
+
+ def __repr__(self):
+ """A string representation of SELF."""
+
+ def full_typename(obj):
+ module = type(obj).__module__
+ qualname = type(obj).__qualname__
+
+ if module is None or module == "builtins":
+ return qualname
+ else:
+ return module + "." + qualname
+
+ return "<" + full_typename(self) + " name='" + self._name + "'>"
diff --git a/gdb/testsuite/gdb.python/py-style-parameter-set.exp b/gdb/testsuite/gdb.python/py-style-parameter-set.exp
new file mode 100644
index 00000000000..73d94d1bb51
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-style-parameter-set.exp
@@ -0,0 +1,366 @@
+# 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.StyleParameterSet.
+
+load_lib gdb-python.exp
+
+require allow_python_tests
+
+# Create a regexp that can be used to match the output of a 'show style
+# NAME' command. HAS_INTENSITY is a boolean and indicates if style NAME has
+# an intensity attribute.
+proc gen_show_style_re { name has_intensity } {
+ set output \
+ [list \
+ "style ${name} background: The \"${name}\" style background color is: none" \
+ "style ${name} foreground: The \"${name}\" style foreground color is: none"]
+
+ if { $has_intensity } {
+ lappend output \
+ "style ${name} intensity: The \"${name}\" style display intensity is: normal"
+ }
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'set style NAME' command,
+# that is, a 'set' command that doesn't actually set an attribute of a
+# style, in this case GDB will print some useful help text. HAS_INTENSITY is
+# a boolean and indicates if style NAME has an intensity attribute or not.
+proc gen_set_style_re { name has_intensity } {
+ set output \
+ [list \
+ "List of \"set style $name\" subcommands:" \
+ "" \
+ "set style $name background -- Set the background color for this property\\." \
+ "set style $name foreground -- Set the foreground color for this property\\."]
+
+ if { $has_intensity } {
+ lappend output \
+ "set style $name intensity -- Set the display intensity for this property\\."
+ }
+
+ lappend output \
+ "" \
+ "Type \"help set style $name\" followed by subcommand name for full documentation\\." \
+ "Type \"apropos word\" to search for commands related to \"word\"\\." \
+ "Type \"apropos -v word\" for full documentation of commands related to \"word\"\\." \
+ "Command name abbreviations are allowed if unambiguous\\."
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'help show style NAME'
+# command. HAS_INTENSITY is a boolean and indicates if style NAME has an
+# intensity attribute or not. DOC is a regexp that matches the doc string
+# for style NAME.
+proc gen_help_show_style_re { name has_intensity doc } {
+ set output \
+ [list \
+ $doc \
+ "" \
+ "List of \"show style ${name}\" subcommands:" \
+ "" \
+ "show style $name background -- Show the background color for this property\\." \
+ "show style $name foreground -- Show the foreground color for this property\\."]
+ if { $has_intensity } {
+ lappend output \
+ "show style $name intensity -- Show the display intensity for this property\\."
+ }
+
+ lappend output \
+ "" \
+ "Type \"help show style $name\" followed by subcommand name for full documentation\\." \
+ "Type \"apropos word\" to search for commands related to \"word\"\\." \
+ "Type \"apropos -v word\" for full documentation of commands related to \"word\"\\." \
+ "Command name abbreviations are allowed if unambiguous\\."
+
+ return [multi_line {*}$output]
+}
+
+# Create a regexp to match against the output of a 'help set style NAME'
+# command. HAS_INTENSITY is a boolean and indicates if style NAME has an
+# intensity attribute or not. DOC is a regexp that matches the doc string
+# for style NAME.
+proc gen_help_set_style_re { name has_intensity doc } {
+ return \
+ [multi_line \
+ $doc \
+ "" \
+ [gen_set_style_re $name $has_intensity]]
+}
+
+# Create styles with and without intensity. Use named and unnamed
+# argument passing, and vary the argument passing order. Create
+# styles with and without documentation.
+#
+# Confirm that the styles contain the expected sub-commands, and that
+# the documentation is as expected.
+proc_with_prefix test_basic_usage {} {
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"style-1\")" \
+ "create style-1"
+
+ gdb_test_no_output \
+ "python style_2 = gdb.StyleParameterSet(add_intensity=True, name=\"style-2\")" \
+ "create style-2"
+
+ gdb_test_no_output \
+ "python style_3 = gdb.StyleParameterSet(name=\"style-3\", doc=\"Style-3 display styling.\\nThis is a multi-line documentation\\nstring describing style-3.\")" \
+ "create style-3"
+
+ gdb_test_no_output \
+ "python style_4 = gdb.StyleParameterSet(\"style-4\", add_intensity=False)" \
+ "create style-4"
+
+ foreach style { style-1 style-2 style-3 } {
+ gdb_test "show style $style" \
+ [gen_show_style_re $style true]
+ gdb_test "set style $style" \
+ [gen_set_style_re $style true]
+ }
+
+ gdb_test "show style style-4" \
+ [gen_show_style_re "style-4" false]
+
+ foreach style { style-1 style-2 } {
+ gdb_test "help show style $style" \
+ [gen_help_show_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+
+
+ set out [gen_help_set_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+
+ gdb_test "help set style $style" \
+ [gen_help_set_style_re $style true \
+ [multi_line \
+ "The $style display styling\\." \
+ "Configure $style colors and display intensity\\."]]
+ }
+
+ gdb_test "help show style style-3" \
+ [gen_help_show_style_re "style-3" true \
+ [multi_line \
+ "Style-3 display styling\\." \
+ "This is a multi-line documentation" \
+ "string describing style-3\\."]]
+
+ gdb_test "help show style style-4" \
+ [gen_help_show_style_re "style-4" false \
+ [multi_line \
+ "The style-4 display styling\\." \
+ "Configure style-4 colors and display intensity\\."]]
+
+ for { set i 1 } { $i < 5 } { incr i } {
+ gdb_test "python print(style_$i)" \
+ "<gdb.StyleParameterSet name='style-$i'>" \
+ "print repr of style_$i"
+ }
+
+ # There is no 'style-4 intensity' property.
+ gdb_test "show style style-4 foreground" \
+ "The \"style-4\" style foreground color is: none"
+ gdb_test "show style style-4 background" \
+ "The \"style-4\" style background color is: none"
+ gdb_test "show style style-4 intensity" \
+ "Undefined show style style-4 command: \"intensity\"\\. Try \"help show style style-4\"\\."
+
+ # There is a 'style-1 intensity' property.
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: none" \
+ "show style-1 foreground before changes"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: none" \
+ "show style-1 background before changes"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: normal" \
+ "show style-1 intensity before changes"
+
+ # Grab the gdb.Style objects from 'style-1'.
+ gdb_test_no_output "python s1 = style_1.style"
+ gdb_test_no_output "python s2 = style_1.value"
+
+ # Check both represent the same style.
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=none, bg=none, intensity=normal>" \
+ "print s1 style before changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=none, bg=none, intensity=normal>" \
+ "print s2 style before changes"
+
+ gdb_test_no_output \
+ "python s1.foreground=gdb.Color('red')" "set foreground"
+ gdb_test_no_output \
+ "python s1.background=gdb.Color('blue')" "set background"
+ gdb_test_no_output \
+ "python s1.intensity=gdb.INTENSITY_DIM" "set intensity"
+
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=red, bg=blue, intensity=dim>" \
+ "print s1 style after first set of changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=red, bg=blue, intensity=dim>" \
+ "print s2 style after first set of changes"
+
+ # Check the style properties have updated.
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: red" \
+ "show style-1 foreground after first set of changes"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: blue" \
+ "show style-1 background after first set of changes"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: dim" \
+ "show style-1 intensity after first set of changes"
+
+ # Change the style properties, check gdb.Style objects update.
+ gdb_test_no_output "set style style-1 foreground yellow"
+ gdb_test_no_output "set style style-1 background cyan"
+ gdb_test_no_output "set style style-1 intensity bold"
+
+ gdb_test "python print(s1)" \
+ "<gdb.Style name='style-1', fg=yellow, bg=cyan, intensity=bold>" \
+ "print s1 style after second set of changes"
+ gdb_test "python print(s2)" \
+ "<gdb.Style name='style-1', fg=yellow, bg=cyan, intensity=bold>" \
+ "print s2 style after second set of changes"
+
+ # Assign a gdb.Style to set 'style-1'. First create some unnamed
+ # style objects that can be used.
+ gdb_test_no_output \
+ "python uns1 = gdb.Style(foreground=gdb.Color('white'), background=gdb.Color('black'), intensity=gdb.INTENSITY_BOLD)" \
+ "create uns1"
+ gdb_test_no_output \
+ "python uns2 = gdb.Style(foreground=gdb.Color('black'), background=gdb.Color('white'), intensity=gdb.INTENSITY_DIM)" \
+ "create uns2"
+
+ gdb_test_no_output "python style_1.value = uns1"
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: white" \
+ "show style-1 foreground after assigning uns1"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: black" \
+ "show style-1 background after assigning uns1"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: bold" \
+ "show style-1 intensity after assigning uns1"
+
+ gdb_test_no_output "python style_1.style = uns2"
+ gdb_test "show style style-1 foreground" \
+ "The \"style-1\" style foreground color is: black" \
+ "show style-1 foreground after assigning uns2"
+ gdb_test "show style style-1 background" \
+ "The \"style-1\" style background color is: white" \
+ "show style-1 background after assigning uns2"
+ gdb_test "show style style-1 intensity" \
+ "The \"style-1\" style display intensity is: dim" \
+ "show style-1 intensity after assigning uns2"
+
+ # Assign a style with an intensity that is not 'NORMAL' to a
+ # StyleParameterSet that doesn't have an intensity. The new
+ # intensity setting should be ignored.
+ gdb_test_no_output "python style_4.style = uns1"
+ gdb_test "show style style-4 foreground" \
+ "The \"style-4\" style foreground color is: white" \
+ "show style-4 foreground after assigning uns1"
+ gdb_test "show style style-4 background" \
+ "The \"style-4\" style background color is: black" \
+ "show style-4 background after assigning uns1"
+ gdb_test "show style style-4 intensity" \
+ "Undefined show style style-4 command: \"intensity\"\\. Try \"help show style style-4\"\\." \
+ "show style-4 intensity after assigning uns1"
+
+ gdb_test "python print(style_4.style)" \
+ "<gdb.Style name='style-4', fg=white, bg=black, intensity=normal>" \
+ "print string repr of style_4's style"
+}
+
+# Test creating a style prefix with gdb.ParameterPrefix, then adding
+# some styles within the new prefix. Change the style through the CLI
+# and confirm that the associated Python object updated as expected.
+proc_with_prefix test_style_prefix {} {
+ gdb_test_no_output \
+ "python gdb.ParameterPrefix(\"style my-style-group\", gdb.COMMAND_NONE)"
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"my-style-group style-1\")" \
+ "create style-1"
+ gdb_test_no_output \
+ "python style_2 = gdb.StyleParameterSet(\"my-style-group style-2\")" \
+ "create style-2"
+
+ gdb_test "python print(style_1.style)" \
+ "<gdb.Style name='my-style-group style-1', fg=none, bg=none, intensity=normal>" \
+ "print 'my-style-group style-1' style before changes"
+ gdb_test "python print(style_2.style)" \
+ "<gdb.Style name='my-style-group style-2', fg=none, bg=none, intensity=normal>" \
+ "print 'my-style-group style-2' style before changes"
+
+ gdb_test_no_output "set style my-style-group style-1 foreground red"
+ gdb_test_no_output "set style my-style-group style-1 background yellow"
+ gdb_test_no_output "set style my-style-group style-1 intensity bold"
+ gdb_test_no_output "set style my-style-group style-2 foreground black"
+ gdb_test_no_output "set style my-style-group style-2 background blue"
+ gdb_test_no_output "set style my-style-group style-2 intensity dim"
+
+ gdb_test "python print(style_1.style)" \
+ "<gdb.Style name='my-style-group style-1', fg=red, bg=yellow, intensity=bold>" \
+ "print 'my-style-group style-1' style after changes"
+ gdb_test "python print(style_2.style)" \
+ "<gdb.Style name='my-style-group style-2', fg=black, bg=blue, intensity=dim>" \
+ "print 'my-style-group style-2' style after changes"
+}
+
+# Test that gdb.StyleParameterSet.apply() works as expected.
+proc_with_prefix test_applying {} {
+ # Create a new StyleParameterSet, and adjust its settings.
+ gdb_test_no_output \
+ "python style_1 = gdb.StyleParameterSet(\"style-1\")" \
+ "create style-1"
+ gdb_test_no_output \
+ "python uns1 = gdb.Style(foreground=gdb.Color('red'), background=gdb.Color('blue'), intensity=gdb.INTENSITY_BOLD)" \
+ "create uns1"
+ gdb_test_no_output "python style_1 = uns1"
+
+ # When styling is off (which it currently is), no escape sequences
+ # should be added.
+ gdb_test \
+ "python print(style_1.apply('xxx'))" "^xxx" \
+ "apply StyleParameterSet to a string when styling is off"
+
+ # When styling is on, we should see an escape sequence added.
+ gdb_test "with style enabled on -- python print(style_1.apply('xxx'))" \
+ "\033\\\[31;44;1;23;24;27mxxx\033\\\[m" \
+ "apply a style when styling is on"
+}
+
+# Start GDB.
+with_ansi_styling_terminal {
+ clean_restart
+}
+
+# Turn styling off so that the output of 'show style ...' isn't styled, this
+# makes it easier to match the output.
+gdb_test_no_output "set style enabled off"
+
+# Run the tests.
+test_basic_usage
+test_style_prefix
+test_applying
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* [PATCHv9 3/3] gdb/python: extend gdb.write to support styled output
2025-09-23 16:54 ` [PATCHv9 0/3] new gdb.Style API Andrew Burgess
2025-09-23 16:54 ` [PATCHv9 1/3] gdb/python: add gdb.Style class Andrew Burgess
2025-09-23 16:54 ` [PATCHv9 2/3] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
@ 2025-09-23 16:54 ` Andrew Burgess
2025-10-03 19:27 ` [PATCHv9 0/3] new gdb.Style API Tom Tromey
3 siblings, 0 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-09-23 16:54 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess, Eli Zaretskii
It is already possible to produce styled output from Python by
converting the gdb.Style to its escape code sequence, and writing that
to the output stream.
But this commit adds an alternative option to the mix by extending the
existing gdb.write() function to accept a 'style' argument. The value
of this argument can be 'None' to indicate no style change should be
performed, this is the default, and matches the existing behaviour.
Or the new 'style' argument can be a gdb.Style object, in which case
the specified style is applied only for the string passed to
gdb.write, after which the default style is re-applied.
Using gdb.write with a style object more closely matches how GDB
handles styling internally, and has the benefit that the user doesn't
need to remember to restore the default style when they are done.
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
---
gdb/NEWS | 3 +
gdb/doc/python.texi | 9 ++-
gdb/python/py-style.c | 21 +++++++
gdb/python/python-internal.h | 14 +++++
gdb/python/python.c | 30 ++++++++--
| 56 +++++++++++++++++++
| 16 ++++++
gdb/testsuite/gdb.python/py-style.exp | 29 ++++++++++
8 files changed, 172 insertions(+), 6 deletions(-)
diff --git a/gdb/NEWS b/gdb/NEWS
index 6d83083f600..3c6f8e9759e 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -190,6 +190,9 @@ info threads [-gid] [-stopped] [-running] [ID]...
Use gdb.StyleParameterSet(NAME) to create 'set style NAME ...'
and 'show style NAME ...' parameters.
+ ** The gdb.write() function now takes an additional, optional,
+ 'style' argument, which can be used to style the output.
+
** 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 45e53845c6e..79f15a67287 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -452,7 +452,7 @@ Basic Python
historical compatibility.
@end defun
-@defun gdb.write (string @r{[}, stream@r{]})
+@defun gdb.write (string @r{[}, stream@r{]} @r{[}, style@r{]})
Print a string to @value{GDBN}'s paginated output stream. The
optional @var{stream} determines the stream to print to. The default
stream is @value{GDBN}'s standard output stream. Possible stream
@@ -475,6 +475,13 @@ Basic Python
@value{GDBN}'s log stream (@pxref{Logging Output}).
@end table
+The @var{style} should be a @code{gdb.Style} object (@pxref{Styles In
+Python}), or @code{None} (the default). If @var{style} is @code{None}
+then the current style for @var{stream} will be applied to @var{text}.
+If @var{style} is a @code{gdb.Style} object, then this style is
+applied to @var{text}, after which the default output style is
+restored.
+
Writing to @code{sys.stdout} or @code{sys.stderr} will automatically
call this function and will automatically direct the output to the
relevant stream.
diff --git a/gdb/python/py-style.c b/gdb/python/py-style.c
index 51b35f2fa7e..cf65e3115b2 100644
--- a/gdb/python/py-style.c
+++ b/gdb/python/py-style.c
@@ -353,6 +353,15 @@ stylepy_init (PyObject *self, PyObject *args, PyObject *kwargs)
\f
+/* See python-internal.h. */
+
+bool
+gdbpy_is_style (PyObject *obj)
+{
+ gdb_assert (obj != nullptr);
+ return PyObject_TypeCheck (obj, &style_object_type);
+}
+
/* Return the ui_file_style for STYLEPY. If the style cannot be found,
then return an empty optional, and set a Python error. */
@@ -369,6 +378,18 @@ stylepy_to_style (style_object *stylepy)
return style;
}
+/* See python-internal.h. */
+
+std::optional<ui_file_style>
+gdbpy_style_object_to_ui_file_style (PyObject *obj)
+{
+ gdb_assert (obj != nullptr);
+ gdb_assert (gdbpy_is_style (obj));
+
+ style_object *style_obj = (style_object *) obj;
+ return stylepy_to_style (style_obj);
+}
+
/* 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
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index f61a1753ac4..dbb2d7eb7e0 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -570,6 +570,20 @@ struct symtab_and_line *sal_object_to_symtab_and_line (PyObject *obj);
frame_info_ptr frame_object_to_frame_info (PyObject *frame_obj);
struct gdbarch *arch_object_to_gdbarch (PyObject *obj);
+/* Return true if OBJ is a gdb.Style object. OBJ must not be NULL. */
+
+extern bool gdbpy_is_style (PyObject *obj);
+
+/* Return the ui_file_style from OBJ, a gdb.Style object. OBJ must not be
+ NULL.
+
+ It is possible that OBJ is a gdb.Style object, but the underlying style
+ cannot be fetched for some reason. If this happens then a Python error
+ is set and an empty optional is returned. */
+
+extern std::optional<ui_file_style>
+ gdbpy_style_object_to_ui_file_style (PyObject *obj);
+
extern PyObject *gdbpy_execute_mi_command (PyObject *self, PyObject *args,
PyObject *kw);
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 740b1968742..51e7a0aa165 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -1567,12 +1567,22 @@ static PyObject *
gdbpy_write (PyObject *self, PyObject *args, PyObject *kw)
{
const char *arg;
- static const char *keywords[] = { "text", "stream", NULL };
+ static const char *keywords[] = { "text", "stream", "style", nullptr };
int stream_type = 0;
+ PyObject *style_obj = Py_None;
- if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s|i", keywords, &arg,
- &stream_type))
- return NULL;
+ if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s|iO", keywords, &arg,
+ &stream_type, &style_obj))
+ return nullptr;
+
+ if (style_obj != Py_None && !gdbpy_is_style (style_obj))
+ {
+ PyErr_Format
+ (PyExc_TypeError,
+ _("'style' argument must be gdb.Style or None, not %s."),
+ Py_TYPE (style_obj)->tp_name);
+ return nullptr;
+ }
try
{
@@ -1590,7 +1600,17 @@ gdbpy_write (PyObject *self, PyObject *args, PyObject *kw)
break;
}
- gdb_puts (arg, stream);
+ if (style_obj == Py_None)
+ gdb_puts (arg, stream);
+ else
+ {
+ std::optional<ui_file_style> style
+ = gdbpy_style_object_to_ui_file_style (style_obj);
+ if (!style.has_value ())
+ return nullptr;
+
+ fputs_styled (arg, style.value (), stream);
+ }
}
catch (const gdb_exception &except)
{
--git a/gdb/testsuite/gdb.python/py-color-pagination.exp b/gdb/testsuite/gdb.python/py-color-pagination.exp
index 1974dbb4186..6b10f9ca793 100644
--- a/gdb/testsuite/gdb.python/py-color-pagination.exp
+++ b/gdb/testsuite/gdb.python/py-color-pagination.exp
@@ -131,8 +131,64 @@ proc test_pagination { type mode } {
}
}
+# Run the command 'style-fill-v2' which fills the screen with output and
+# triggers the pagination prompt. Check that styling is applied correctly
+# to the output. This v2 command is exercising passing a style to
+# gdb.write() rather than passing the escape sequence for the style.
+proc test_pagination_v2 { } {
+ set saw_bad_color_handling false
+ set expected_restore_color ""
+ set last_color ""
+ gdb_test_multiple "style-fill-v2" "" {
+ -re "^style-fill-v2\r\n" {
+ exp_continue
+ }
+
+ -re "^(${::any_color}\033\\\[m)(${::any_color})$::str\033\\\[m" {
+ # After a continuation prompt GDB will restore the previous
+ # color, and then we immediately switch to a new color.
+ set restored_color $expect_out(1,string)
+ if { $restored_color ne $expected_restore_color } {
+ set saw_bad_color_handling true
+ }
+ set last_color $expect_out(2,string)
+ exp_continue
+ }
+
+ -re "^(${::any_color})$::str\033\\\[m" {
+ # This pattern matches printing STR in all cases that are not
+ # immediately after a pagination prompt. In this case there is
+ # a single escape sequence to set the color.
+ set last_color $expect_out(1,string)
+ exp_continue
+ }
+
+ -re "^$::pagination_prompt$" {
+ # After a pagination prompt we expect GDB to restore the last
+ # color, but this will then be disabled due to a styled
+ # gdb.write emitting a return to default style escape sequence.
+ set expected_restore_color "$last_color\033\[m"
+
+ # Send '\n' to view more output.
+ send_gdb "\n"
+ exp_continue
+ }
+
+ -re "^\r\n" {
+ # The matches the newline sent to the continuation prompt.
+ exp_continue
+ }
+
+ -re "^$::gdb_prompt $" {
+ gdb_assert { !$saw_bad_color_handling } $gdb_test_name
+ }
+ }
+}
+
foreach_with_prefix type { color style } {
foreach_with_prefix mode { write print } {
test_pagination $type $mode
}
}
+
+test_pagination_v2
--git a/gdb/testsuite/gdb.python/py-color-pagination.py b/gdb/testsuite/gdb.python/py-color-pagination.py
index f0252e5bf86..9cdc76cb0ce 100644
--- a/gdb/testsuite/gdb.python/py-color-pagination.py
+++ b/gdb/testsuite/gdb.python/py-color-pagination.py
@@ -62,5 +62,21 @@ class StyleTester(gdb.Command):
write(mode, "\n")
+class StyleTester2(gdb.Command):
+ def __init__(self):
+ super().__init__("style-fill-v2", gdb.COMMAND_USER)
+
+ def invoke(self, args, from_tty):
+ str = "<" + "-" * 78 + ">"
+ for i in range(0, 20):
+ for color_name in basic_colors:
+ c = gdb.Color(color_name)
+ s = gdb.Style(foreground=c)
+ gdb.write(str, style=s)
+
+ gdb.write("\n")
+
+
ColorTester()
StyleTester()
+StyleTester2()
diff --git a/gdb/testsuite/gdb.python/py-style.exp b/gdb/testsuite/gdb.python/py-style.exp
index b2efe9a97e3..491e189774e 100644
--- a/gdb/testsuite/gdb.python/py-style.exp
+++ b/gdb/testsuite/gdb.python/py-style.exp
@@ -340,3 +340,32 @@ gdb_test_no_output "python input_text = \"a totally different string that is als
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"
+
+# Test gdb.write passing in a style. Define a helper function to
+# ensure all output is flushed before we return to the prompt.
+gdb_test_multiline "create function to call gdb.write then flush" \
+ "python" "" \
+ "def write_and_flush(*args, **kwargs):" "" \
+ " gdb.write(*args, **kwargs)" "" \
+ " gdb.write(\"\\n\")" "" \
+ " gdb.flush(gdb.STDOUT)" "" \
+ "end" ""
+
+gdb_test "python write_and_flush(\"some text\")" \
+ "^some text" "unstyled text, no style passed"
+
+gdb_test "python write_and_flush(\"some text\", style=None)" \
+ "^some text" "unstyled text, pass style as None"
+
+gdb_test "python write_and_flush(\"some text\", style=filename_style)" \
+ "^\033\\\[34;41;2;23;24;27msome text\033\\\[m" \
+ "styled output, pass style by keyword"
+
+gdb_test "python write_and_flush(\"some text\", gdb.STDOUT, filename_style)" \
+ "^\033\\\[34;41;2;23;24;27msome text\033\\\[m" \
+ "styled output, pass style by position"
+
+gdb_test "python write_and_flush(\"some text\", style='filename')" \
+ [multi_line \
+ "Python Exception <class 'TypeError'>: 'style' argument must be gdb\\.Style or None, not str\\." \
+ "Error occurred in Python: 'style' argument must be gdb\\.Style or None, not str\\."]
--
2.47.1
^ permalink raw reply [flat|nested] 61+ messages in thread
* Re: [PATCHv8 1/3] gdb/python: add gdb.Style class
2025-09-16 16:47 ` Tom Tromey
@ 2025-09-23 16:59 ` Andrew Burgess
0 siblings, 0 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-09-23 16:59 UTC (permalink / raw)
To: Tom Tromey; +Cc: gdb-patches, Eli Zaretskii
Tom Tromey <tom@tromey.com> writes:
>>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:
>
> Andrew> +static std::optional<ui_file_style>
> Andrew> +stylepy_style_from_name (const char *name, bool *has_intensity_ptr = nullptr,
> Andrew> + const cmd_list_element **found_cmd_ptr = nullptr)
> Andrew> +{
> Andrew> + std::string cmd_str = std::string ("show style ") + name;
> Andrew> +
> Andrew> + struct cmd_list_element *cmd = nullptr;
> Andrew> + struct cmd_list_element *alias = nullptr;
> Andrew> + int found;
> Andrew> + try
> Andrew> + {
> Andrew> + struct cmd_list_element *prefix;
> Andrew> + found = lookup_cmd_composition (cmd_str.c_str (), &alias, &prefix, &cmd);
> Andrew> + }
> Andrew> + catch (const gdb_exception &ex)
> Andrew> + {
> Andrew> + PyErr_Format (PyExc_RuntimeError,
> Andrew> + _("style '%s' cannot be found."), name);
> Andrew> + return {};
>
> Noting this for a later comment...
>
> Andrew> +static PyObject *
> Andrew> +stylepy_apply (PyObject *self, PyObject *args, PyObject *kw)
> Andrew> +{
> Andrew> + style_object *style_obj = (style_object *) self;
> Andrew> +
> Andrew> + static const char *keywords[] = { "string", nullptr };
> Andrew> + PyObject *input_obj;
> Andrew> +
> Andrew> + if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "O!", keywords,
> Andrew> + &PyUnicode_Type, &input_obj))
> Andrew> + return nullptr;
>
> Why "O!" and not "s" here?
This is possibly a little premature optimisation, but capturing the
string as O! means I get a 'PyObject *'. In the case where styling is
not applied I can then just Py_INCREF that object and return it.
If I capture the argument with "s" then I get a 'char *' back, so in the
case where no styling is being added I have to create a new PyObject by
converting the string _back_ again.
I've left this code as it is for now, but I have added a comment. If
you dislike this then just let me know, it's not essential, and I can
switch to using "s".
>
> Andrew> + gdb::unique_xmalloc_ptr<char>
> Andrew> + input (python_string_to_target_string (input_obj));
>
> target string seems like an unusual choice here.
Yeah, especially as I use host_string_to_python_string about 5 lines
later to convert the adjusted string back to a Python object. I've
fixed this to use python_string_to_host_string.
>
> Andrew> + std::string output
> Andrew> + = string_printf ("%s%s%s", style->to_ansi ().c_str (), input.get (),
> Andrew> + ui_file_style ().to_ansi ().c_str ());
>
> This can just using "+" which seems a little cleaner.
Done.
>
> Andrew> + /* Handle named styles. This is harder, we need to write to the actual
> Andrew> + parameter. First though, look up the named style to see if it has an
> Andrew> + intensity. HAS_INTENSITY will be set true only if the style exists,
> Andrew> + and has an 'intensity' setting. */
> Andrew> +
> Andrew> + bool has_intensity = false;
> Andrew> + (void) stylepy_style_from_name (style_obj->style_name, &has_intensity);
> Andrew> + if (!has_intensity)
> Andrew> + {
> Andrew> + PyErr_Format
> Andrew> + (PyExc_ValueError, "the intensity of style '%s' is not writable.",
> Andrew> + style_obj->style_name);
> Andrew> + return -1;
>
> I guess I don't know the Python rule here, but it seems like if
> stylepy_style_from_name returns {}, then this can set an exception while
> one is already set. See the snippet above.
>
> Instead of the (void) I think it would be a bit better to just check the
> return result of stylepy_style_from_name, and if it returns an error,
> just return -1 directly; and only then do the has_intensity check.
You are absolutely correct. I fixed this, capturing the return value
and checking we found a style.
I've posted a v9 series with all these fixes.
Thanks,
Andrew
^ permalink raw reply [flat|nested] 61+ messages in thread
* Re: [PATCHv8 3/3] gdb/python: extend gdb.write to support styled output
2025-09-16 16:56 ` Tom Tromey
@ 2025-09-23 17:05 ` Andrew Burgess
2025-09-23 18:38 ` Tom Tromey
0 siblings, 1 reply; 61+ messages in thread
From: Andrew Burgess @ 2025-09-23 17:05 UTC (permalink / raw)
To: Tom Tromey; +Cc: gdb-patches, Eli Zaretskii
Tom Tromey <tom@tromey.com> writes:
>>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:
>
> Andrew> Using gdb.write with a style object more closely matches how GDB
> Andrew> handles styling internally, and has the benefit that the user doesn't
> Andrew> need to remember to restore the default style when they are done.
>
> I'm mildly meh on this one since I tend to think users should use print
> and that gdb.write is an implementation detail. But since it exists...
>
> Andrew> + return PyObject_TypeCheck (obj, &style_object_type);
>
> Andrew> +/* See python-internal.h. */
> Andrew> +
> Andrew> +std::optional<ui_file_style>
> Andrew> +gdbpy_style_object_to_ui_file_style (PyObject *obj)
> Andrew> +{
> Andrew> + gdb_assert (obj != nullptr);
> Andrew> + if (!gdbpy_is_style (obj))
> Andrew> + {
> Andrew> + PyErr_Format
> Andrew> + (PyExc_TypeError, _("argument must be a gdb.Style object, not %s."),
> Andrew> + Py_TYPE (obj)->tp_name);
> Andrew> + return {};
> Andrew> + }
> Andrew> +
> Andrew> + style_object *style_obj = (style_object *) obj;
> Andrew> + return stylepy_to_style (style_obj);
>
>
> Andrew> + if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s|iO", keywords, &arg,
> Andrew> + &stream_type, &style_obj))
> Andrew> + return nullptr;
> Andrew> +
> Andrew> + if (style_obj != Py_None && !gdbpy_is_style (style_obj))
> Andrew> + {
> Andrew> + PyErr_Format
> Andrew> + (PyExc_TypeError,
> Andrew> + _("'style' argument must be gdb.Style or None, not %s."),
> Andrew> + Py_TYPE (style_obj)->tp_name);
> Andrew> + return nullptr;
>
> FWIW if the type object is available there's a way to get Python to do
> this bit when parsing the arguments.
I think you're referring too 'O!' maybe? The reason that doesn't work
is that the argument can be a gdb.Style _or_ None.
It feels like this must be a common pattern, so I would have expected to
find a modifier that takes a named type or accepts None, but I couldn't
find anything.
Is there another trick that I'm not aware of? Would love to know
because I'm pretty sure I've had to do this elsewhere in GDB when an
argument can also be None.
> This explicit check is fine too, but it seems to me that this is
> redundant with the check that gdbpy_style_object_to_ui_file_style does
> and so this could just delegate to that.
Given gdbpy_style_object_to_ui_file_style is only used in this one
place, what I did in the end was to drop the gdbpy_is_style check from
gdbpy_style_object_to_ui_file_style, replacing it with an assert.
Then I've retained the check in gdbpy_write.
The reason is that the error message in gdbpy_write is so much more
descriptive, it names the argument, and says that the argument can be a
gdb.Style or None. The error from gdbpy_style_object_to_ui_file_style
was much more generic. I prefer accurate error messages wherever
possible.
So, after this change, there's only one gdbpy_is_style check (not
counting the assert), and only one place where the error message is set
if the argument is the wrong type.
I posted a v9 series with these changes in, let me know what you think.
Thanks,
Andrew
^ permalink raw reply [flat|nested] 61+ messages in thread
* Re: [PATCHv8 3/3] gdb/python: extend gdb.write to support styled output
2025-09-23 17:05 ` Andrew Burgess
@ 2025-09-23 18:38 ` Tom Tromey
0 siblings, 0 replies; 61+ messages in thread
From: Tom Tromey @ 2025-09-23 18:38 UTC (permalink / raw)
To: Andrew Burgess; +Cc: Tom Tromey, gdb-patches, Eli Zaretskii
>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:
>> FWIW if the type object is available there's a way to get Python to do
>> this bit when parsing the arguments.
Andrew> I think you're referring too 'O!' maybe? The reason that doesn't work
Andrew> is that the argument can be a gdb.Style _or_ None.
Andrew> It feels like this must be a common pattern, so I would have expected to
Andrew> find a modifier that takes a named type or accepts None, but I couldn't
Andrew> find anything.
Andrew> Is there another trick that I'm not aware of? Would love to know
Andrew> because I'm pretty sure I've had to do this elsewhere in GDB when an
Andrew> argument can also be None.
No, I think you're right, I missed the None bit. Sorry about that.
Tom
^ permalink raw reply [flat|nested] 61+ messages in thread
* Re: [PATCHv9 0/3] new gdb.Style API
2025-09-23 16:54 ` [PATCHv9 0/3] new gdb.Style API Andrew Burgess
` (2 preceding siblings ...)
2025-09-23 16:54 ` [PATCHv9 3/3] gdb/python: extend gdb.write to support styled output Andrew Burgess
@ 2025-10-03 19:27 ` Tom Tromey
2025-10-05 12:51 ` Andrew Burgess
3 siblings, 1 reply; 61+ messages in thread
From: Tom Tromey @ 2025-10-03 19:27 UTC (permalink / raw)
To: Andrew Burgess; +Cc: gdb-patches
>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:
[...]
Andrew> In v9:
Going for a record here.
I think these are ok. Thanks for doing this.
Approved-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 61+ messages in thread
* Re: [PATCHv9 0/3] new gdb.Style API
2025-10-03 19:27 ` [PATCHv9 0/3] new gdb.Style API Tom Tromey
@ 2025-10-05 12:51 ` Andrew Burgess
2025-10-05 15:16 ` Tom de Vries
0 siblings, 1 reply; 61+ messages in thread
From: Andrew Burgess @ 2025-10-05 12:51 UTC (permalink / raw)
To: Tom Tromey; +Cc: gdb-patches
Tom Tromey <tom@tromey.com> writes:
>>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:
>
> [...]
> Andrew> In v9:
>
> Going for a record here.
>
> I think these are ok. Thanks for doing this.
> Approved-By: Tom Tromey <tom@tromey.com>
Pushed.
Thanks,
Andrew
^ permalink raw reply [flat|nested] 61+ messages in thread
* Re: [PATCHv9 0/3] new gdb.Style API
2025-10-05 12:51 ` Andrew Burgess
@ 2025-10-05 15:16 ` Tom de Vries
2025-10-05 17:59 ` Tom Tromey
2025-10-05 18:03 ` Andrew Burgess
0 siblings, 2 replies; 61+ messages in thread
From: Tom de Vries @ 2025-10-05 15:16 UTC (permalink / raw)
To: Andrew Burgess, Tom Tromey; +Cc: gdb-patches
On 10/5/25 14:51, Andrew Burgess wrote:
> Tom Tromey <tom@tromey.com> writes:
>
>>>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:
>>
>> [...]
>> Andrew> In v9:
>>
>> Going for a record here.
>>
>> I think these are ok. Thanks for doing this.
>> Approved-By: Tom Tromey <tom@tromey.com>
>
> Pushed.
Hi,
I'm running into pre-commit failures:
...
flake8...................................................................Failed
- hook id: flake8
- duration: 0.25s
- exit code: 1
gdb/python/lib/gdb/__init__.py:557:31: F405 'Parameter' may be
undefined, or defined from star imports: _gdb
gdb/python/lib/gdb/__init__.py:579:21: F405 'COMMAND_NONE' may be
undefined, or defined from star imports: _gdb
gdb/python/lib/gdb/__init__.py:612:21: F405 'Color' may be undefined, or
defined from star imports: _gdb
gdb/python/lib/gdb/__init__.py:613:21: F405 'PARAM_COLOR' may be
undefined, or defined from star imports: _gdb
...
...
Thanks,
- Tom
^ permalink raw reply [flat|nested] 61+ messages in thread
* Re: [PATCHv9 0/3] new gdb.Style API
2025-10-05 15:16 ` Tom de Vries
@ 2025-10-05 17:59 ` Tom Tromey
2025-10-06 9:01 ` Andrew Burgess
2025-10-05 18:03 ` Andrew Burgess
1 sibling, 1 reply; 61+ messages in thread
From: Tom Tromey @ 2025-10-05 17:59 UTC (permalink / raw)
To: Tom de Vries; +Cc: Andrew Burgess, Tom Tromey, gdb-patches
Tom> I'm running into pre-commit failures:
Tom> ...
I went ahead & fixed this.
Tom
^ permalink raw reply [flat|nested] 61+ messages in thread
* Re: [PATCHv9 0/3] new gdb.Style API
2025-10-05 15:16 ` Tom de Vries
2025-10-05 17:59 ` Tom Tromey
@ 2025-10-05 18:03 ` Andrew Burgess
2025-10-05 18:41 ` Tom de Vries
1 sibling, 1 reply; 61+ messages in thread
From: Andrew Burgess @ 2025-10-05 18:03 UTC (permalink / raw)
To: Tom de Vries, Tom Tromey; +Cc: gdb-patches
Tom de Vries <tdevries@suse.de> writes:
> On 10/5/25 14:51, Andrew Burgess wrote:
>> Tom Tromey <tom@tromey.com> writes:
>>
>>>>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:
>>>
>>> [...]
>>> Andrew> In v9:
>>>
>>> Going for a record here.
>>>
>>> I think these are ok. Thanks for doing this.
>>> Approved-By: Tom Tromey <tom@tromey.com>
>>
>> Pushed.
>
> Hi,
>
> I'm running into pre-commit failures:
I searched on the GDB wiki, and did a grep in gdb/ and gdb/doc/ but
could find nothing about "pre-commit" except for one comment in
check-include-guards.py, but that doesn't say what "pre-commit" is.
Is this process documented somewhere else? Or have I just failed at
finding it?
> ...
> flake8...................................................................Failed
> - hook id: flake8
> - duration: 0.25s
> - exit code: 1
>
> gdb/python/lib/gdb/__init__.py:557:31: F405 'Parameter' may be
> undefined, or defined from star imports: _gdb
> gdb/python/lib/gdb/__init__.py:579:21: F405 'COMMAND_NONE' may be
> undefined, or defined from star imports: _gdb
> gdb/python/lib/gdb/__init__.py:612:21: F405 'Color' may be undefined, or
> defined from star imports: _gdb
> gdb/python/lib/gdb/__init__.py:613:21: F405 'PARAM_COLOR' may be
> undefined, or defined from star imports: _gdb
> ...
The patch below doesn't break GDB's Python support. And based on the
existing code comments, I suspect this is the required fix. But I
haven't run the "pre-commit" tests (see comments above), so you might
one to check this fixes the issue you reported.
Thanks,
Andrew
---
commit 5812afb15e8b7774984c238eb67adcf6eaa704a9
Author: Andrew Burgess <aburgess@redhat.com>
Date: Sun Oct 5 18:55:33 2025 +0100
gdb/python: fix some flake8 warnings related to Python code
After these commits:
* 49e4d0cdca3 gdb/python: add gdb.Style class
* d5214580a5f gdb/python: new class gdb.StyleParameterSet
* 3c724596812 gdb/python: extend gdb.write to support styled output
the following issues were pointed out on the mailing list:
I'm running into pre-commit failures:
...
flake8...................................................................Failed
- hook id: flake8
- duration: 0.25s
- exit code: 1
gdb/python/lib/gdb/__init__.py:557:31: F405 'Parameter' may be undefined, or defined from star imports: _gdb
gdb/python/lib/gdb/__init__.py:579:21: F405 'COMMAND_NONE' may be undefined, or defined from star imports: _gdb
gdb/python/lib/gdb/__init__.py:612:21: F405 'Color' may be undefined, or defined from star imports: _gdb
gdb/python/lib/gdb/__init__.py:613:21: F405 'PARAM_COLOR' may be undefined, or defined from star imports: _gdb
I believe this patch should resolve these issues.
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index 0388c6a2c00..65f76988925 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -34,6 +34,10 @@ from _gdb import (
parameter,
selected_inferior,
write,
+ Parameter,
+ COMMAND_NONE,
+ Color,
+ PARAM_COLOR,
)
# isort: split
^ permalink raw reply [flat|nested] 61+ messages in thread
* Re: [PATCHv9 0/3] new gdb.Style API
2025-10-05 18:03 ` Andrew Burgess
@ 2025-10-05 18:41 ` Tom de Vries
2025-10-06 9:01 ` Andrew Burgess
0 siblings, 1 reply; 61+ messages in thread
From: Tom de Vries @ 2025-10-05 18:41 UTC (permalink / raw)
To: Andrew Burgess, Tom Tromey; +Cc: gdb-patches
On 10/5/25 20:03, Andrew Burgess wrote:
> Tom de Vries <tdevries@suse.de> writes:
>
>> On 10/5/25 14:51, Andrew Burgess wrote:
>>> Tom Tromey <tom@tromey.com> writes:
>>>
>>>>>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:
>>>>
>>>> [...]
>>>> Andrew> In v9:
>>>>
>>>> Going for a record here.
>>>>
>>>> I think these are ok. Thanks for doing this.
>>>> Approved-By: Tom Tromey <tom@tromey.com>
>>>
>>> Pushed.
>>
>> Hi,
>>
>> I'm running into pre-commit failures:
>
> I searched on the GDB wiki, and did a grep in gdb/ and gdb/doc/ but
> could find nothing about "pre-commit" except for one comment in
> check-include-guards.py, but that doesn't say what "pre-commit" is.
>
> Is this process documented somewhere else? Or have I just failed at
> finding it?
>
Hi Andrew,
AFAIK there's not really a process, that is, there's no formal agreement
on a requirement to run it. [ See also this email thread (
https://sourceware.org/pipermail/gdb-patches/2025-October/221320.html )
on the status of tclint as pre-commit hook. ]
There's a file called .pre-commit-config.yaml in the root dir of the
repository. I have added some information in there on how to install
pre-commit, and how to skip some/all checks.
Thanks,
- Tom
>> ...
>> flake8...................................................................Failed
>> - hook id: flake8
>> - duration: 0.25s
>> - exit code: 1
>>
>> gdb/python/lib/gdb/__init__.py:557:31: F405 'Parameter' may be
>> undefined, or defined from star imports: _gdb
>> gdb/python/lib/gdb/__init__.py:579:21: F405 'COMMAND_NONE' may be
>> undefined, or defined from star imports: _gdb
>> gdb/python/lib/gdb/__init__.py:612:21: F405 'Color' may be undefined, or
>> defined from star imports: _gdb
>> gdb/python/lib/gdb/__init__.py:613:21: F405 'PARAM_COLOR' may be
>> undefined, or defined from star imports: _gdb
>> ...
>
> The patch below doesn't break GDB's Python support. And based on the
> existing code comments, I suspect this is the required fix. But I
> haven't run the "pre-commit" tests (see comments above), so you might
> one to check this fixes the issue you reported.
>
> Thanks,
> Andrew
>
> ---
>
> commit 5812afb15e8b7774984c238eb67adcf6eaa704a9
> Author: Andrew Burgess <aburgess@redhat.com>
> Date: Sun Oct 5 18:55:33 2025 +0100
>
> gdb/python: fix some flake8 warnings related to Python code
>
> After these commits:
>
> * 49e4d0cdca3 gdb/python: add gdb.Style class
> * d5214580a5f gdb/python: new class gdb.StyleParameterSet
> * 3c724596812 gdb/python: extend gdb.write to support styled output
>
> the following issues were pointed out on the mailing list:
>
> I'm running into pre-commit failures:
> ...
> flake8...................................................................Failed
> - hook id: flake8
> - duration: 0.25s
> - exit code: 1
>
> gdb/python/lib/gdb/__init__.py:557:31: F405 'Parameter' may be undefined, or defined from star imports: _gdb
> gdb/python/lib/gdb/__init__.py:579:21: F405 'COMMAND_NONE' may be undefined, or defined from star imports: _gdb
> gdb/python/lib/gdb/__init__.py:612:21: F405 'Color' may be undefined, or defined from star imports: _gdb
> gdb/python/lib/gdb/__init__.py:613:21: F405 'PARAM_COLOR' may be undefined, or defined from star imports: _gdb
>
> I believe this patch should resolve these issues.
>
> diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
> index 0388c6a2c00..65f76988925 100644
> --- a/gdb/python/lib/gdb/__init__.py
> +++ b/gdb/python/lib/gdb/__init__.py
> @@ -34,6 +34,10 @@ from _gdb import (
> parameter,
> selected_inferior,
> write,
> + Parameter,
> + COMMAND_NONE,
> + Color,
> + PARAM_COLOR,
> )
>
> # isort: split
>
^ permalink raw reply [flat|nested] 61+ messages in thread
* Re: [PATCHv9 0/3] new gdb.Style API
2025-10-05 18:41 ` Tom de Vries
@ 2025-10-06 9:01 ` Andrew Burgess
2025-10-06 12:29 ` Tom de Vries
0 siblings, 1 reply; 61+ messages in thread
From: Andrew Burgess @ 2025-10-06 9:01 UTC (permalink / raw)
To: Tom de Vries, Tom Tromey; +Cc: gdb-patches
Tom de Vries <tdevries@suse.de> writes:
> On 10/5/25 20:03, Andrew Burgess wrote:
>> Tom de Vries <tdevries@suse.de> writes:
>>
>>> On 10/5/25 14:51, Andrew Burgess wrote:
>>>> Tom Tromey <tom@tromey.com> writes:
>>>>
>>>>>>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:
>>>>>
>>>>> [...]
>>>>> Andrew> In v9:
>>>>>
>>>>> Going for a record here.
>>>>>
>>>>> I think these are ok. Thanks for doing this.
>>>>> Approved-By: Tom Tromey <tom@tromey.com>
>>>>
>>>> Pushed.
>>>
>>> Hi,
>>>
>>> I'm running into pre-commit failures:
>>
>> I searched on the GDB wiki, and did a grep in gdb/ and gdb/doc/ but
>> could find nothing about "pre-commit" except for one comment in
>> check-include-guards.py, but that doesn't say what "pre-commit" is.
>>
>> Is this process documented somewhere else? Or have I just failed at
>> finding it?
>>
>
> Hi Andrew,
>
> AFAIK there's not really a process, that is, there's no formal agreement
> on a requirement to run it. [ See also this email thread (
> https://sourceware.org/pipermail/gdb-patches/2025-October/221320.html )
> on the status of tclint as pre-commit hook. ]
>
> There's a file called .pre-commit-config.yaml in the root dir of the
> repository. I have added some information in there on how to install
> pre-commit, and how to skip some/all checks.
Thanks for the pointer.
Out of interest, is there any reason why these tests couldn't be (also
be) added as actual tests to GDB's testsuite?
I haven't tried setting up the infrastructure needed to run these tests
using the pre-commit framework yet, but I'm guessing the time needed to
run these tests is insignificant compared to the existing GDB testsuite.
Thanks,
Andrew
>
> Thanks,
> - Tom
>
>>> ...
>>> flake8...................................................................Failed
>>> - hook id: flake8
>>> - duration: 0.25s
>>> - exit code: 1
>>>
>>> gdb/python/lib/gdb/__init__.py:557:31: F405 'Parameter' may be
>>> undefined, or defined from star imports: _gdb
>>> gdb/python/lib/gdb/__init__.py:579:21: F405 'COMMAND_NONE' may be
>>> undefined, or defined from star imports: _gdb
>>> gdb/python/lib/gdb/__init__.py:612:21: F405 'Color' may be undefined, or
>>> defined from star imports: _gdb
>>> gdb/python/lib/gdb/__init__.py:613:21: F405 'PARAM_COLOR' may be
>>> undefined, or defined from star imports: _gdb
>>> ...
>>
>> The patch below doesn't break GDB's Python support. And based on the
>> existing code comments, I suspect this is the required fix. But I
>> haven't run the "pre-commit" tests (see comments above), so you might
>> one to check this fixes the issue you reported.
>>
>> Thanks,
>> Andrew
>>
>> ---
>>
>> commit 5812afb15e8b7774984c238eb67adcf6eaa704a9
>> Author: Andrew Burgess <aburgess@redhat.com>
>> Date: Sun Oct 5 18:55:33 2025 +0100
>>
>> gdb/python: fix some flake8 warnings related to Python code
>>
>> After these commits:
>>
>> * 49e4d0cdca3 gdb/python: add gdb.Style class
>> * d5214580a5f gdb/python: new class gdb.StyleParameterSet
>> * 3c724596812 gdb/python: extend gdb.write to support styled output
>>
>> the following issues were pointed out on the mailing list:
>>
>> I'm running into pre-commit failures:
>> ...
>> flake8...................................................................Failed
>> - hook id: flake8
>> - duration: 0.25s
>> - exit code: 1
>>
>> gdb/python/lib/gdb/__init__.py:557:31: F405 'Parameter' may be undefined, or defined from star imports: _gdb
>> gdb/python/lib/gdb/__init__.py:579:21: F405 'COMMAND_NONE' may be undefined, or defined from star imports: _gdb
>> gdb/python/lib/gdb/__init__.py:612:21: F405 'Color' may be undefined, or defined from star imports: _gdb
>> gdb/python/lib/gdb/__init__.py:613:21: F405 'PARAM_COLOR' may be undefined, or defined from star imports: _gdb
>>
>> I believe this patch should resolve these issues.
>>
>> diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
>> index 0388c6a2c00..65f76988925 100644
>> --- a/gdb/python/lib/gdb/__init__.py
>> +++ b/gdb/python/lib/gdb/__init__.py
>> @@ -34,6 +34,10 @@ from _gdb import (
>> parameter,
>> selected_inferior,
>> write,
>> + Parameter,
>> + COMMAND_NONE,
>> + Color,
>> + PARAM_COLOR,
>> )
>>
>> # isort: split
>>
^ permalink raw reply [flat|nested] 61+ messages in thread
* Re: [PATCHv9 0/3] new gdb.Style API
2025-10-05 17:59 ` Tom Tromey
@ 2025-10-06 9:01 ` Andrew Burgess
0 siblings, 0 replies; 61+ messages in thread
From: Andrew Burgess @ 2025-10-06 9:01 UTC (permalink / raw)
To: Tom Tromey, Tom de Vries; +Cc: Tom Tromey, gdb-patches
Tom Tromey <tom@tromey.com> writes:
> Tom> I'm running into pre-commit failures:
> Tom> ...
>
> I went ahead & fixed this.
Thanks Tom.
Thanks,
Andrew
^ permalink raw reply [flat|nested] 61+ messages in thread
* Re: [PATCHv9 0/3] new gdb.Style API
2025-10-06 9:01 ` Andrew Burgess
@ 2025-10-06 12:29 ` Tom de Vries
0 siblings, 0 replies; 61+ messages in thread
From: Tom de Vries @ 2025-10-06 12:29 UTC (permalink / raw)
To: Andrew Burgess, Tom Tromey; +Cc: gdb-patches
On 10/6/25 11:01, Andrew Burgess wrote:
> Tom de Vries <tdevries@suse.de> writes:
>
>> On 10/5/25 20:03, Andrew Burgess wrote:
>>> Tom de Vries <tdevries@suse.de> writes:
>>>
>>>> On 10/5/25 14:51, Andrew Burgess wrote:
>>>>> Tom Tromey <tom@tromey.com> writes:
>>>>>
>>>>>>>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:
>>>>>>
>>>>>> [...]
>>>>>> Andrew> In v9:
>>>>>>
>>>>>> Going for a record here.
>>>>>>
>>>>>> I think these are ok. Thanks for doing this.
>>>>>> Approved-By: Tom Tromey <tom@tromey.com>
>>>>>
>>>>> Pushed.
>>>>
>>>> Hi,
>>>>
>>>> I'm running into pre-commit failures:
>>>
>>> I searched on the GDB wiki, and did a grep in gdb/ and gdb/doc/ but
>>> could find nothing about "pre-commit" except for one comment in
>>> check-include-guards.py, but that doesn't say what "pre-commit" is.
>>>
>>> Is this process documented somewhere else? Or have I just failed at
>>> finding it?
>>>
>>
>> Hi Andrew,
>>
>> AFAIK there's not really a process, that is, there's no formal agreement
>> on a requirement to run it. [ See also this email thread (
>> https://sourceware.org/pipermail/gdb-patches/2025-October/221320.html )
>> on the status of tclint as pre-commit hook. ]
>>
>> There's a file called .pre-commit-config.yaml in the root dir of the
>> repository. I have added some information in there on how to install
>> pre-commit, and how to skip some/all checks.
>
> Thanks for the pointer.
>
> Out of interest, is there any reason why these tests couldn't be (also
> be) added as actual tests to GDB's testsuite?
>
> I haven't tried setting up the infrastructure needed to run these tests
> using the pre-commit framework yet, but I'm guessing the time needed to
> run these tests is insignificant compared to the existing GDB testsuite.
>
Hi Andrew,
I gave it a try (
https://sourceware.org/pipermail/gdb-patches/2025-October/221445.html ).
The new test-case takes about 5 seconds on my laptop.
The test-case cannot do anything if pre-commit is not installed, so it's
unsupported in that case.
But it doesn't require the pre-commit git hooks to be installed. So the
test will work even if you didn't do "pre-commit install".
OTOH, it does require a git repo, so the test will be unsupported on a
snapshot.
Thanks,
- Tom
> Thanks,
> Andrew
>
>
>
>>
>> Thanks,
>> - Tom
>>
>>>> ...
>>>> flake8...................................................................Failed
>>>> - hook id: flake8
>>>> - duration: 0.25s
>>>> - exit code: 1
>>>>
>>>> gdb/python/lib/gdb/__init__.py:557:31: F405 'Parameter' may be
>>>> undefined, or defined from star imports: _gdb
>>>> gdb/python/lib/gdb/__init__.py:579:21: F405 'COMMAND_NONE' may be
>>>> undefined, or defined from star imports: _gdb
>>>> gdb/python/lib/gdb/__init__.py:612:21: F405 'Color' may be undefined, or
>>>> defined from star imports: _gdb
>>>> gdb/python/lib/gdb/__init__.py:613:21: F405 'PARAM_COLOR' may be
>>>> undefined, or defined from star imports: _gdb
>>>> ...
>>>
>>> The patch below doesn't break GDB's Python support. And based on the
>>> existing code comments, I suspect this is the required fix. But I
>>> haven't run the "pre-commit" tests (see comments above), so you might
>>> one to check this fixes the issue you reported.
>>>
>>> Thanks,
>>> Andrew
>>>
>>> ---
>>>
>>> commit 5812afb15e8b7774984c238eb67adcf6eaa704a9
>>> Author: Andrew Burgess <aburgess@redhat.com>
>>> Date: Sun Oct 5 18:55:33 2025 +0100
>>>
>>> gdb/python: fix some flake8 warnings related to Python code
>>>
>>> After these commits:
>>>
>>> * 49e4d0cdca3 gdb/python: add gdb.Style class
>>> * d5214580a5f gdb/python: new class gdb.StyleParameterSet
>>> * 3c724596812 gdb/python: extend gdb.write to support styled output
>>>
>>> the following issues were pointed out on the mailing list:
>>>
>>> I'm running into pre-commit failures:
>>> ...
>>> flake8...................................................................Failed
>>> - hook id: flake8
>>> - duration: 0.25s
>>> - exit code: 1
>>>
>>> gdb/python/lib/gdb/__init__.py:557:31: F405 'Parameter' may be undefined, or defined from star imports: _gdb
>>> gdb/python/lib/gdb/__init__.py:579:21: F405 'COMMAND_NONE' may be undefined, or defined from star imports: _gdb
>>> gdb/python/lib/gdb/__init__.py:612:21: F405 'Color' may be undefined, or defined from star imports: _gdb
>>> gdb/python/lib/gdb/__init__.py:613:21: F405 'PARAM_COLOR' may be undefined, or defined from star imports: _gdb
>>>
>>> I believe this patch should resolve these issues.
>>>
>>> diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
>>> index 0388c6a2c00..65f76988925 100644
>>> --- a/gdb/python/lib/gdb/__init__.py
>>> +++ b/gdb/python/lib/gdb/__init__.py
>>> @@ -34,6 +34,10 @@ from _gdb import (
>>> parameter,
>>> selected_inferior,
>>> write,
>>> + Parameter,
>>> + COMMAND_NONE,
>>> + Color,
>>> + PARAM_COLOR,
>>> )
>>>
>>> # isort: split
>>>
>
^ permalink raw reply [flat|nested] 61+ messages in thread
end of thread, other threads:[~2025-10-06 12:35 UTC | newest]
Thread overview: 61+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-06-04 9:39 [PATCH 0/2] Python Style API Andrew Burgess
2025-06-04 9:39 ` [PATCH 1/2] gdb/python: add gdb.Style class Andrew Burgess
2025-06-04 9:39 ` [PATCH 2/2] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
2025-06-06 13:38 ` [PATCHv2 0/2] Python Style API Andrew Burgess
2025-06-06 13:38 ` [PATCHv2 1/2] gdb/python: add gdb.Style class Andrew Burgess
2025-06-06 14:21 ` Eli Zaretskii
2025-06-06 13:38 ` [PATCHv2 2/2] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
2025-06-07 10:44 ` [PATCHv3 0/2] Python Style API Andrew Burgess
2025-06-07 10:44 ` [PATCHv3 1/2] gdb/python: add gdb.Style class Andrew Burgess
2025-06-07 10:44 ` [PATCHv3 2/2] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
2025-06-09 15:54 ` [PATCHv3 0/2] Python Style API Andrew Burgess
2025-06-18 19:30 ` [PATCHv4 0/4] " Andrew Burgess
2025-06-18 19:30 ` [PATCHv4 1/4] gdb: allow gdb.Color to work correctly with pagination Andrew Burgess
2025-06-18 19:30 ` [PATCHv4 2/4] gdb/python: add gdb.Style class Andrew Burgess
2025-06-18 19:30 ` [PATCHv4 3/4] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
2025-06-18 19:30 ` [PATCHv4 4/4] gdb: extend gdb.write to support styled output Andrew Burgess
2025-06-25 14:42 ` [PATCHv5 0/4] Python Style API Andrew Burgess
2025-06-25 14:42 ` [PATCHv5 1/4] gdb: allow gdb.Color to work correctly with pagination Andrew Burgess
2025-06-25 14:42 ` [PATCHv5 2/4] gdb/python: add gdb.Style class Andrew Burgess
2025-06-25 14:42 ` [PATCHv5 3/4] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
2025-06-25 14:42 ` [PATCHv5 4/4] gdb/python: extend gdb.write to support styled output Andrew Burgess
2025-07-07 14:43 ` [PATCHv6 0/4] Python Style API Andrew Burgess
2025-07-07 14:43 ` [PATCHv6 1/4] gdb: allow gdb.Color to work correctly with pagination Andrew Burgess
2025-07-07 14:43 ` [PATCHv6 2/4] gdb/python: add gdb.Style class Andrew Burgess
2025-07-07 14:43 ` [PATCHv6 3/4] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
2025-07-07 14:43 ` [PATCHv6 4/4] gdb/python: extend gdb.write to support styled output Andrew Burgess
2025-08-13 10:25 ` [PATCHv7 0/4] Fix for gdb.Color + pagination AND new gdb.Style API Andrew Burgess
2025-08-13 10:25 ` [PATCHv7 1/4] gdb: allow gdb.Color to work correctly with pagination Andrew Burgess
2025-08-24 16:13 ` Andrew Burgess
2025-08-25 10:21 ` Tom de Vries
2025-08-26 14:35 ` [PATCH] gdb/testsuite: work around empty substring bug in expect Andrew Burgess
2025-08-27 6:24 ` Tom de Vries
2025-08-13 10:29 ` [PATCHv7 2/4] gdb/python: add gdb.Style class Andrew Burgess
2025-08-13 10:30 ` [PATCHv7 4/4] gdb/python: extend gdb.write to support styled output Andrew Burgess
2025-08-13 12:37 ` Eli Zaretskii
2025-08-13 10:34 ` [PATCHv7 3/4] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
2025-08-13 13:05 ` Eli Zaretskii
2025-08-13 13:05 ` Eli Zaretskii
2025-08-27 11:34 ` [PATCHv8 0/3] new gdb.Style API Andrew Burgess
2025-08-27 11:34 ` [PATCHv8 1/3] gdb/python: add gdb.Style class Andrew Burgess
2025-09-16 16:47 ` Tom Tromey
2025-09-23 16:59 ` Andrew Burgess
2025-08-27 11:34 ` [PATCHv8 2/3] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
2025-09-16 16:50 ` Tom Tromey
2025-08-27 11:34 ` [PATCHv8 3/3] gdb/python: extend gdb.write to support styled output Andrew Burgess
2025-09-16 16:56 ` Tom Tromey
2025-09-23 17:05 ` Andrew Burgess
2025-09-23 18:38 ` Tom Tromey
2025-09-23 16:54 ` [PATCHv9 0/3] new gdb.Style API Andrew Burgess
2025-09-23 16:54 ` [PATCHv9 1/3] gdb/python: add gdb.Style class Andrew Burgess
2025-09-23 16:54 ` [PATCHv9 2/3] gdb/python: new class gdb.StyleParameterSet Andrew Burgess
2025-09-23 16:54 ` [PATCHv9 3/3] gdb/python: extend gdb.write to support styled output Andrew Burgess
2025-10-03 19:27 ` [PATCHv9 0/3] new gdb.Style API Tom Tromey
2025-10-05 12:51 ` Andrew Burgess
2025-10-05 15:16 ` Tom de Vries
2025-10-05 17:59 ` Tom Tromey
2025-10-06 9:01 ` Andrew Burgess
2025-10-05 18:03 ` Andrew Burgess
2025-10-05 18:41 ` Tom de Vries
2025-10-06 9:01 ` Andrew Burgess
2025-10-06 12:29 ` Tom de Vries
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox