diff options
Diffstat (limited to 'gdb/python/py-style.c')
-rw-r--r-- | gdb/python/py-style.c | 818 |
1 files changed, 818 insertions, 0 deletions
diff --git a/gdb/python/py-style.c b/gdb/python/py-style.c new file mode 100644 index 0000000..b10a45f --- /dev/null +++ b/gdb/python/py-style.c @@ -0,0 +1,818 @@ +/* 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) +{ + switch (intensity_value) + { + case ui_file_style::NORMAL: + case ui_file_style::DIM: + case ui_file_style::BOLD: + return static_cast<ui_file_style::intensity> (intensity_value); + + default: + PyErr_Format + (PyExc_ValueError, _("invalid 'intensity' value %d."), + intensity_value); + return {}; + } +} + +/* 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; +} + + + +/* 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. */ + +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; +} + +/* 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 + 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; + } +} + + + +/* 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; +} + + + +/* 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); +} + + + +/* 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); |