mirror of
https://github.com/bminor/binutils-gdb.git
synced 2025-11-16 12:34:43 +00:00
The ANSI escape sequence translation layer in TUI mode strips italic
or underlined text modes silently. You cannot output text formatted
like that using `TuiWindow.write` in Python at the moment.
Parse the ANSI escape sequences for italic and underlined text into
the `ui_file_style` structure and apply it to the TUI window when
applying styles, similar to preserving the bold/dim state already.
A script like this shows italic and underlined text correctly now.
```python
import gdb
class TestTUIWindow:
_tui_window: gdb.TuiWindow
def __init__(self, tui_window: gdb.TuiWindow) -> None:
self._tui_window = tui_window
self._tui_window.title = "colors test"
def render(self) -> None:
self._tui_window.write("""
\x1b[4mThis is underlined.\x1b[24m And normal text.
\x1b[3mThis is italic.\x1b[23m And normal text.
""", True)
gdb.register_window_type("colortest", TestTUIWindow)
```
And launching it with
```
source the_above_script.py
tui new-layout test colortest 1 cmd 1
layout test
```
Approved-By: Tom Tromey <tom@tromey.com>
421 lines
10 KiB
C++
421 lines
10 KiB
C++
/* Styling for ui_file
|
|
Copyright (C) 2018-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/>. */
|
|
|
|
#ifndef GDB_UI_STYLE_H
|
|
#define GDB_UI_STYLE_H
|
|
|
|
/* One of the color spaces that usually supported by terminals. */
|
|
enum class color_space
|
|
{
|
|
/* one default terminal color */
|
|
MONOCHROME,
|
|
|
|
/* foreground colors \e[30m ... \e[37m,
|
|
background colors \e[40m ... \e[47m */
|
|
ANSI_8COLOR,
|
|
|
|
/* foreground colors \e[30m ... \e[37m, \e[90m ... \e[97m
|
|
background colors \e[40m ... \e[47m, \e[100m ... \e107m */
|
|
AIXTERM_16COLOR,
|
|
|
|
/* foreground colors \e[38;5;0m ... \e[38;5;255m
|
|
background colors \e[48;5;0m ... \e[48;5;255m */
|
|
XTERM_256COLOR,
|
|
|
|
/* foreground colors \e[38;2;0;0;0m ... \e[38;2;255;255;255m
|
|
background colors \e[48;2;0;0;0m ... \e[48;2;255;255;255m */
|
|
RGB_24BIT
|
|
};
|
|
|
|
/* Color spaces supported by terminal. */
|
|
extern const std::vector<color_space> & colorsupport ();
|
|
|
|
/* Textual representation of C. */
|
|
extern const char * color_space_name (color_space c);
|
|
|
|
/* Cast C to RESULT and return true if it's value is valid; false otherwise. */
|
|
extern bool color_space_safe_cast (color_space *result, long c);
|
|
|
|
/* Styles that can be applied to a ui_file. */
|
|
struct ui_file_style
|
|
{
|
|
/* One of the basic colors that can be handled by ANSI
|
|
terminals. */
|
|
enum basic_color
|
|
{
|
|
NONE = -1,
|
|
BLACK,
|
|
RED,
|
|
GREEN,
|
|
YELLOW,
|
|
BLUE,
|
|
MAGENTA,
|
|
CYAN,
|
|
WHITE
|
|
};
|
|
|
|
/* Representation of a terminal color. */
|
|
class color
|
|
{
|
|
public:
|
|
|
|
color (basic_color c)
|
|
: m_color_space (c == NONE ? color_space::MONOCHROME
|
|
: color_space::ANSI_8COLOR),
|
|
m_value (c)
|
|
{
|
|
}
|
|
|
|
color (int c)
|
|
: m_value (c)
|
|
{
|
|
if (c < -1 || c > 255)
|
|
error (_("Palette color index %d is out of range."), c);
|
|
if (c == -1)
|
|
m_color_space = color_space::MONOCHROME;
|
|
else if (c <= 7)
|
|
m_color_space = color_space::ANSI_8COLOR;
|
|
else if (c <= 15)
|
|
m_color_space = color_space::AIXTERM_16COLOR;
|
|
else
|
|
m_color_space = color_space::XTERM_256COLOR;
|
|
}
|
|
|
|
color (color_space cs, int c)
|
|
: m_color_space (cs),
|
|
m_value (c)
|
|
{
|
|
if (c < -1 || c > 255)
|
|
error (_("Palette color index %d is out of range."), c);
|
|
|
|
std::pair<int, int> range;
|
|
switch (cs)
|
|
{
|
|
case color_space::MONOCHROME:
|
|
range = {-1, -1};
|
|
break;
|
|
case color_space::ANSI_8COLOR:
|
|
range = {0, 7};
|
|
break;
|
|
case color_space::AIXTERM_16COLOR:
|
|
range = {0, 15};
|
|
break;
|
|
case color_space::XTERM_256COLOR:
|
|
range = {0, 255};
|
|
break;
|
|
default:
|
|
error (_("Color space %d is incompatible with indexed colors."),
|
|
static_cast<int> (cs));
|
|
}
|
|
|
|
if (c < range.first || c > range.second)
|
|
error (_("Color %d is out of range [%d, %d] of color space %d."),
|
|
c, range.first, range.second, static_cast<int> (cs));
|
|
}
|
|
|
|
color (uint8_t r, uint8_t g, uint8_t b)
|
|
: m_color_space (color_space::RGB_24BIT),
|
|
m_red (r),
|
|
m_green (g),
|
|
m_blue (b)
|
|
{
|
|
}
|
|
|
|
bool operator== (const color &other) const
|
|
{
|
|
if (m_color_space != other.m_color_space)
|
|
return false;
|
|
if (is_simple ())
|
|
return m_value == other.m_value;
|
|
return (m_red == other.m_red && m_green == other.m_green
|
|
&& m_blue == other.m_blue);
|
|
}
|
|
|
|
bool operator!= (const color &other) const
|
|
{
|
|
return ! (*this == other);
|
|
}
|
|
|
|
/* Compute a simple hash code for this object. */
|
|
size_t hash () const
|
|
{
|
|
if (is_simple ())
|
|
return m_value;
|
|
return (m_red << 16) + (m_green << 8) + m_red;
|
|
}
|
|
|
|
color_space colorspace () const
|
|
{
|
|
return m_color_space;
|
|
}
|
|
|
|
/* Return true if this is the "NONE" color, false otherwise. */
|
|
bool is_none () const
|
|
{
|
|
return m_color_space == color_space::MONOCHROME && m_value == NONE;
|
|
}
|
|
|
|
/* Return true if this is one of the basic colors, false
|
|
otherwise. */
|
|
bool is_basic () const
|
|
{
|
|
if (m_color_space == color_space::ANSI_8COLOR
|
|
|| m_color_space == color_space::AIXTERM_16COLOR)
|
|
return BLACK <= m_value && m_value <= WHITE;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/* Return true if this is one of the colors, stored as int, false
|
|
otherwise. */
|
|
bool is_simple () const
|
|
{
|
|
return m_color_space != color_space::RGB_24BIT;
|
|
}
|
|
|
|
/* Return true if this is one of the indexed colors, false
|
|
otherwise. */
|
|
bool is_indexed () const
|
|
{
|
|
return m_color_space != color_space::RGB_24BIT
|
|
&& m_color_space != color_space::MONOCHROME;
|
|
}
|
|
|
|
/* Return true if this is one of the direct colors (RGB, CMY, CMYK), false
|
|
otherwise. */
|
|
bool is_direct () const
|
|
{
|
|
return m_color_space == color_space::RGB_24BIT;
|
|
}
|
|
|
|
/* Return the value of a simple color. */
|
|
int get_value () const
|
|
{
|
|
gdb_assert (is_simple ());
|
|
return m_value;
|
|
}
|
|
|
|
/* Fill in RGB with the red/green/blue values for this color.
|
|
This may not be called for basic colors or for the "NONE"
|
|
color. */
|
|
void get_rgb (uint8_t *rgb) const;
|
|
|
|
/* Append the ANSI terminal escape sequence for this color to STR.
|
|
IS_FG indicates whether this is a foreground or background
|
|
color. */
|
|
void append_ansi (bool is_fg, std::string *str) const;
|
|
|
|
/* Return the ANSI escape sequence for this color.
|
|
IS_FG indicates whether this is a foreground or background color. */
|
|
std::string to_ansi (bool is_fg) const;
|
|
|
|
/* Returns text representation of this object.
|
|
It is "none", name of a basic color, number or a #RRGGBB hex triplet. */
|
|
std::string to_string () const;
|
|
|
|
/* Approximates THIS color by closest one from SPACES. */
|
|
color approximate (const std::vector<color_space> &spaces) const;
|
|
|
|
private:
|
|
|
|
color_space m_color_space;
|
|
union
|
|
{
|
|
int m_value;
|
|
struct
|
|
{
|
|
uint8_t m_red, m_green, m_blue;
|
|
};
|
|
};
|
|
};
|
|
|
|
/* Intensity settings that are available. */
|
|
enum intensity
|
|
{
|
|
NORMAL = 0,
|
|
BOLD,
|
|
DIM
|
|
};
|
|
|
|
ui_file_style () = default;
|
|
|
|
ui_file_style (color f, color b, intensity i = NORMAL)
|
|
: m_foreground (f),
|
|
m_background (b),
|
|
m_intensity (i)
|
|
{
|
|
}
|
|
|
|
bool operator== (const ui_file_style &other) const
|
|
{
|
|
return (m_foreground == other.m_foreground
|
|
&& m_background == other.m_background
|
|
&& m_intensity == other.m_intensity
|
|
&& m_reverse == other.m_reverse
|
|
&& m_italic == other.m_italic
|
|
&& m_underline == other.m_underline);
|
|
}
|
|
|
|
bool operator!= (const ui_file_style &other) const
|
|
{
|
|
return !(*this == other);
|
|
}
|
|
|
|
/* Return the ANSI escape sequence for this style. */
|
|
std::string to_ansi () const;
|
|
|
|
/* Return true if this style is the default style; false
|
|
otherwise. */
|
|
bool is_default () const
|
|
{
|
|
return (m_foreground == NONE
|
|
&& m_background == NONE
|
|
&& m_intensity == NORMAL
|
|
&& !m_reverse
|
|
&& !m_italic
|
|
&& !m_underline);
|
|
}
|
|
|
|
/* Return true if this style specified reverse display; false
|
|
otherwise. */
|
|
bool is_reverse () const
|
|
{
|
|
return m_reverse;
|
|
}
|
|
|
|
/* Set/clear the reverse display flag. */
|
|
void set_reverse (bool reverse)
|
|
{
|
|
m_reverse = reverse;
|
|
}
|
|
|
|
/* Return the foreground color of this style. */
|
|
const color &get_foreground () const
|
|
{
|
|
return m_foreground;
|
|
}
|
|
|
|
/* Set the foreground color of this style. */
|
|
void set_fg (color c)
|
|
{
|
|
m_foreground = c;
|
|
}
|
|
|
|
/* Return the background color of this style. */
|
|
const color &get_background () const
|
|
{
|
|
return m_background;
|
|
}
|
|
|
|
/* Set the background color of this style. */
|
|
void set_bg (color c)
|
|
{
|
|
m_background = c;
|
|
}
|
|
|
|
/* Return the intensity of this style. */
|
|
intensity get_intensity () const
|
|
{
|
|
return m_intensity;
|
|
}
|
|
|
|
/* Return true if this style specified italic display; false
|
|
otherwise. */
|
|
bool is_italic () const
|
|
{
|
|
return m_italic;
|
|
}
|
|
|
|
/* Set/clear the italic display flag. */
|
|
void set_italic (bool italic)
|
|
{
|
|
m_italic = italic;
|
|
}
|
|
|
|
/* Return true if this style specified underline display; false
|
|
otherwise. */
|
|
bool is_underline () const
|
|
{
|
|
return m_underline;
|
|
}
|
|
|
|
/* Set/clear the underline display flag. */
|
|
void set_underline (bool underline)
|
|
{
|
|
m_underline = underline;
|
|
}
|
|
|
|
/* 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
|
|
case, N_READ is updated to reflect the number of chars read from
|
|
BUF. */
|
|
bool parse (const char *buf, size_t *n_read);
|
|
|
|
/* We need this because we can't pass a reference via va_args. */
|
|
const ui_file_style *ptr () const
|
|
{
|
|
return this;
|
|
}
|
|
|
|
/* nullptr-terminated list of names corresponding to enum basic_color. */
|
|
static const std::vector<const char *> basic_color_enums;
|
|
|
|
private:
|
|
|
|
color m_foreground = NONE;
|
|
color m_background = NONE;
|
|
intensity m_intensity = NORMAL;
|
|
bool m_italic = false;
|
|
bool m_underline = false;
|
|
bool m_reverse = false;
|
|
};
|
|
|
|
/* Possible results for checking an ANSI escape sequence. */
|
|
enum class ansi_escape_result
|
|
{
|
|
/* The escape sequence is definitely not recognizable. */
|
|
NO_MATCH,
|
|
|
|
/* The escape sequence might be recognizable with more input. */
|
|
INCOMPLETE,
|
|
|
|
/* The escape sequence is definitely recognizable. */
|
|
MATCHED,
|
|
};
|
|
|
|
/* Examine an ANSI escape sequence in BUF. BUF must begin with an ESC
|
|
character. Return a value indicating whether the sequence was
|
|
recognizable. If MATCHED is returned, then N_READ is updated to
|
|
reflect the number of chars read from BUF. */
|
|
|
|
extern ansi_escape_result examine_ansi_escape (const char *buf, int *n_read);
|
|
|
|
/* Skip an ANSI escape sequence in BUF. BUF must begin with an ESC
|
|
character. Return true if an escape sequence was successfully
|
|
skipped; false otherwise. If an escape sequence was skipped,
|
|
N_READ is updated to reflect the number of chars read from BUF. */
|
|
|
|
static inline bool
|
|
skip_ansi_escape (const char *buf, int *n_read)
|
|
{
|
|
return examine_ansi_escape (buf, n_read) == ansi_escape_result::MATCHED;
|
|
}
|
|
|
|
#endif /* GDB_UI_STYLE_H */
|