Introduce ui_file_style
authorTom Tromey <tom@tromey.com>
Sat, 10 Nov 2018 00:29:50 +0000 (17:29 -0700)
committerTom Tromey <tom@tromey.com>
Fri, 28 Dec 2018 19:49:48 +0000 (12:49 -0700)
This introduces the new ui_file_style class and various helpers.  This
class represents a terminal style and provides methods for parsing and
emitting the corresponding ANSI terminal escape sequences.

gdb/ChangeLog
2018-12-28  Tom Tromey  <tom@tromey.com>

* unittests/style-selftests.c: New file.
* ui-style.c: New file.
* ui-style.h: New file.
* ui-file.h: Include ui-style.h.
* Makefile.in (COMMON_SFILES): Add ui-style.c.
(HFILES_NO_SRCDIR): Add ui-style.h.
(SUBDIR_UNITTESTS_SRCS): Add style-selftests.c.

gdb/ChangeLog
gdb/Makefile.in
gdb/ui-file.h
gdb/ui-style.c [new file with mode: 0644]
gdb/ui-style.h [new file with mode: 0644]
gdb/unittests/style-selftests.c [new file with mode: 0644]

index 0616275..6e7e7f2 100644 (file)
@@ -1,5 +1,15 @@
 2018-12-28  Tom Tromey  <tom@tromey.com>
 
+       * unittests/style-selftests.c: New file.
+       * ui-style.c: New file.
+       * ui-style.h: New file.
+       * ui-file.h: Include ui-style.h.
+       * Makefile.in (COMMON_SFILES): Add ui-style.c.
+       (HFILES_NO_SRCDIR): Add ui-style.h.
+       (SUBDIR_UNITTESTS_SRCS): Add style-selftests.c.
+
+2018-12-28  Tom Tromey  <tom@tromey.com>
+
        * command.h (add_setshow_enum_cmd): Add "context" argument.
        * cli/cli-decode.c (add_setshow_enum_cmd): Add "context"
        argument.  Call set_cmd_context.
index bbff688..ec81263 100644 (file)
@@ -426,6 +426,7 @@ SUBDIR_UNITTESTS_SRCS = \
        unittests/scoped_mmap-selftests.c \
        unittests/scoped_restore-selftests.c \
        unittests/string_view-selftests.c \
+       unittests/style-selftests.c \
        unittests/tracepoint-selftests.c \
        unittests/unpack-selftests.c \
        unittests/utils-selftests.c \
@@ -1125,6 +1126,7 @@ COMMON_SFILES = \
        typeprint.c \
        ui-file.c \
        ui-out.c \
+       ui-style.c \
        user-regs.c \
        utils.c \
        valarith.c \
@@ -1398,6 +1400,7 @@ HFILES_NO_SRCDIR = \
        typeprint.h \
        ui-file.h \
        ui-out.h \
+       ui-style.h \
        user-regs.h \
        utils.h \
        valprint.h \
index 2cf5f83..b780dff 100644 (file)
@@ -20,6 +20,7 @@
 #define UI_FILE_H
 
 #include <string>
+#include "ui-style.h"
 
 /* The abstract ui_file base class.  */
 
diff --git a/gdb/ui-style.c b/gdb/ui-style.c
new file mode 100644 (file)
index 0000000..059824f
--- /dev/null
@@ -0,0 +1,413 @@
+/* Styling for ui_file
+   Copyright (C) 2018 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 "defs.h"
+#include "ui-style.h"
+
+/* A regular expression that is used for matching ANSI terminal escape
+   sequences.  */
+
+static const char *ansi_regex_text =
+  /* Introduction.  */
+  "^\033\\["
+#define DATA_SUBEXP 1
+  /* Capture parameter and intermediate bytes.  */
+  "("
+  /* Parameter bytes.  */
+  "[\x30-\x3f]*"
+  /* Intermediate bytes.  */
+  "[\x20-\x2f]*"
+  /* End the first capture.  */
+  ")"
+  /* The final byte.  */
+#define FINAL_SUBEXP 2
+  "([\x40-\x7e])";
+
+/* The number of subexpressions to allocate space for, including the
+   "0th" whole match subexpression.  */
+#define NUM_SUBEXPRESSIONS 3
+
+/* The compiled form of ansi_regex_text.  */
+
+static regex_t ansi_regex;
+
+/* This maps bright colors to RGB triples.  The index is the bright
+   color index, starting with bright black.  The values come from
+   xterm.  */
+
+static const uint8_t bright_colors[][3] = {
+  { 127, 127, 127 },           /* Black.  */
+  { 255, 0, 0 },               /* Red.  */
+  { 0, 255, 0 },               /* Green.  */
+  { 255, 255, 0 },             /* Yellow.  */
+  { 92, 92, 255 },             /* Blue.  */
+  { 255, 0, 255 },             /* Magenta.  */
+  { 0, 255, 255 },             /* Cyan.  */
+  { 255, 255, 255 }            /* White.  */
+};
+
+/* See ui-style.h.  */
+
+bool
+ui_file_style::color::append_ansi (bool is_fg, std::string *str) const
+{
+  if (m_simple)
+    {
+      if (m_value >= BLACK && m_value <= WHITE)
+       str->append (std::to_string (m_value + (is_fg ? 30 : 40)));
+      else if (m_value > WHITE && m_value <= WHITE + 8)
+       str->append (std::to_string (m_value - WHITE + (is_fg ? 90 : 100)));
+      else if (m_value != -1)
+       {
+         str->append (is_fg ? "38;5;" : "48;5;");
+         str->append (std::to_string (m_value));
+       }
+      else
+       return false;
+    }
+  else
+    {
+      str->append (is_fg ? "38;2;" : "48;2;");
+      str->append (std::to_string (m_red)
+                  + ";" + std::to_string (m_green)
+                  + ";" + std::to_string (m_blue));
+    }
+  return true;
+}
+
+/* See ui-style.h.  */
+
+void
+ui_file_style::color::get_rgb (uint8_t *rgb) const
+{
+  if (m_simple)
+    {
+      /* Can't call this for a basic color or NONE -- those will end
+        up in the assert below.  */
+      if (m_value >= 8 && m_value <= 15)
+       memcpy (rgb, bright_colors[m_value - 8], 3 * sizeof (uint8_t));
+      else if (m_value >= 16 && m_value <= 231)
+       {
+         int value = m_value;
+         value -= 16;
+         /* This obscure formula seems to be what terminals actually
+            do.  */
+         int component = value / 36;
+         rgb[0] = component == 0 ? 0 : (55 + component * 40);
+         value %= 36;
+         component = value / 6;
+         rgb[1] = component == 0 ? 0 : (55 + component * 40);
+         value %= 6;
+         rgb[2] = value == 0 ? 0 : (55 + value * 40);
+       }
+      else if (m_value >= 232)
+       {
+         uint8_t v = (m_value - 232) * 10 + 8;
+         rgb[0] = v;
+         rgb[1] = v;
+         rgb[2] = v;
+       }
+      else
+       gdb_assert_not_reached ("get_rgb called on invalid color");
+    }
+  else
+    {
+      rgb[0] = m_red;
+      rgb[1] = m_green;
+      rgb[2] = m_blue;
+    }
+}
+
+/* See ui-style.h.  */
+
+std::string
+ui_file_style::to_ansi () const
+{
+  std::string result ("\033[");
+  bool need_semi = m_foreground.append_ansi (true, &result);
+  if (!m_background.is_none ())
+    {
+      if (need_semi)
+       result.push_back (';');
+      m_background.append_ansi (false, &result);
+      need_semi = true;
+    }
+  if (m_intensity != NORMAL)
+    {
+      if (need_semi)
+       result.push_back (';');
+      result.append (std::to_string (m_intensity));
+      need_semi = true;
+    }
+  if (m_reverse)
+    {
+      if (need_semi)
+       result.push_back (';');
+      result.push_back ('7');
+    }
+  result.push_back ('m');
+  return result;
+}
+
+/* Read a ";" and a number from STRING.  Return the number of
+   characters read and put the number into *NUM.  */
+
+static bool
+read_semi_number (const char *string, int *idx, long *num)
+{
+  if (string[*idx] != ';')
+    return false;
+  ++*idx;
+  if (string[*idx] < '0' || string[*idx] > '9')
+    return false;
+  char *tail;
+  *num = strtol (string + *idx, &tail, 10);
+  *idx = tail - string;
+  return true;
+}
+
+/* A helper for ui_file_style::parse that reads an extended color
+   sequence; that is, and 8- or 24- bit color.  */
+
+static bool
+extended_color (const char *str, int *idx, ui_file_style::color *color)
+{
+  long value;
+
+  if (!read_semi_number (str, idx, &value))
+    return false;
+
+  if (value == 5)
+    {
+      /* 8-bit color.  */
+      if (!read_semi_number (str, idx, &value))
+       return false;
+
+      if (value >= 0 && value <= 255)
+       *color = ui_file_style::color (value);
+      else
+       return false;
+    }
+  else if (value == 2)
+    {
+      /* 24-bit color.  */
+      long r, g, b;
+      if (!read_semi_number (str, idx, &r)
+         || r > 255
+         || !read_semi_number (str, idx, &g)
+         || g > 255
+         || !read_semi_number (str, idx, &b)
+         || b > 255)
+       return false;
+      *color = ui_file_style::color (r, g, b);
+    }
+  else
+    {
+      /* Unrecognized sequence.  */
+      return false;
+    }
+
+  return true;
+}
+
+/* See ui-style.h.  */
+
+bool
+ui_file_style::parse (const char *buf, size_t *n_read)
+{
+  regmatch_t subexps[NUM_SUBEXPRESSIONS];
+
+  int match = regexec (&ansi_regex, buf, ARRAY_SIZE (subexps), subexps, 0);
+  if (match == REG_NOMATCH)
+    {
+      *n_read = 0;
+      return false;
+    }
+  /* Other failures mean the regexp is broken.  */
+  gdb_assert (match == 0);
+  /* The regexp is anchored.  */
+  gdb_assert (subexps[0].rm_so == 0);
+  /* The final character exists.  */
+  gdb_assert (subexps[FINAL_SUBEXP].rm_eo - subexps[FINAL_SUBEXP].rm_so == 1);
+
+  if (buf[subexps[FINAL_SUBEXP].rm_so] != 'm')
+    {
+      /* We don't handle this sequence, so just drop it.  */
+      *n_read = subexps[0].rm_eo;
+      return false;
+    }
+
+  /* Examine each setting in the match and apply it to the result.
+     See the Select Graphic Rendition section of
+     https://en.wikipedia.org/wiki/ANSI_escape_code.  In essence each
+     code is just a number, separated by ";"; there are some more
+     wrinkles but we don't support them all..  */
+
+  /* "\033[m" means the same thing as "\033[0m", so handle that
+     specially here.  */
+  if (subexps[DATA_SUBEXP].rm_so == subexps[DATA_SUBEXP].rm_eo)
+    *this = ui_file_style ();
+
+  for (regoff_t i = subexps[DATA_SUBEXP].rm_so;
+       i < subexps[DATA_SUBEXP].rm_eo;
+       ++i)
+    {
+      if (buf[i] == ';')
+       {
+         /* Skip.  */
+       }
+      else if (buf[i] >= '0' && buf[i] <= '9')
+       {
+         char *tail;
+         long value = strtol (buf + i, &tail, 10);
+         i = tail - buf;
+
+         switch (value)
+           {
+           case 0:
+             /* Reset.  */
+             *this = ui_file_style ();
+             break;
+           case 1:
+             /* Bold.  */
+             m_intensity = BOLD;
+             break;
+           case 2:
+             /* Dim.  */
+             m_intensity = DIM;
+             break;
+           case 7:
+             /* Reverse.  */
+             m_reverse = true;
+             break;
+           case 21:
+             m_intensity = NORMAL;
+             break;
+           case 22:
+             /* Normal.  */
+             m_intensity = NORMAL;
+             break;
+           case 27:
+             /* Inverse off.  */
+             m_reverse = false;
+             break;
+
+           case 30:
+           case 31:
+           case 32:
+           case 33:
+           case 34:
+           case 35:
+           case 36:
+           case 37:
+             /* Note: not 38.  */
+           case 39:
+             m_foreground = color (value - 30);
+             break;
+
+           case 40:
+           case 41:
+           case 42:
+           case 43:
+           case 44:
+           case 45:
+           case 46:
+           case 47:
+             /* Note: not 48.  */
+           case 49:
+             m_background = color (value - 40);
+             break;
+
+           case 90:
+           case 91:
+           case 92:
+           case 93:
+           case 94:
+           case 95:
+           case 96:
+           case 97:
+             m_foreground = color (value - 90 + 8);
+             break;
+
+           case 100:
+           case 101:
+           case 102:
+           case 103:
+           case 104:
+           case 105:
+           case 106:
+           case 107:
+             m_background = color (value - 100 + 8);
+             break;
+
+           case 38:
+             /* If we can't parse the extended color, fail.  */
+             if (!extended_color (buf, &i, &m_foreground))
+               {
+                 *n_read = subexps[0].rm_eo;
+                 return false;
+               }
+             break;
+
+           case 48:
+             /* If we can't parse the extended color, fail.  */
+             if (!extended_color (buf, &i, &m_background))
+               {
+                 *n_read = subexps[0].rm_eo;
+                 return false;
+               }
+             break;
+
+           default:
+             /* Ignore everything else.  */
+             break;
+           }
+       }
+      else
+       {
+         /* Unknown, let's just ignore.  */
+       }
+    }
+
+  *n_read = subexps[0].rm_eo;
+  return true;
+}
+
+/* See ui-style.h.  */
+
+bool
+skip_ansi_escape (const char *buf, int *n_read)
+{
+  regmatch_t subexps[NUM_SUBEXPRESSIONS];
+
+  int match = regexec (&ansi_regex, buf, ARRAY_SIZE (subexps), subexps, 0);
+  if (match == REG_NOMATCH || buf[subexps[FINAL_SUBEXP].rm_so] != 'm')
+    return false;
+
+  *n_read = subexps[FINAL_SUBEXP].rm_eo;
+  return true;
+}
+
+void
+_initialize_ui_style ()
+{
+  int code = regcomp (&ansi_regex, ansi_regex_text, REG_EXTENDED);
+  /* If the regular expression was incorrect, it was a programming
+     error.  */
+  gdb_assert (code == 0);
+}
diff --git a/gdb/ui-style.h b/gdb/ui-style.h
new file mode 100644 (file)
index 0000000..2d0e516
--- /dev/null
@@ -0,0 +1,213 @@
+/* Styling for ui_file
+   Copyright (C) 2018 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 UI_STYLE_H
+#define UI_STYLE_H
+
+/* 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_simple (true),
+       m_value (c)
+    {
+    }
+
+    color (int c)
+      : m_simple (true),
+       m_value (c)
+    {
+      gdb_assert (c >= -1 && c <= 255);
+    }
+
+    color (uint8_t r, uint8_t g, uint8_t b)
+      : m_simple (false),
+       m_red (r),
+       m_green (g),
+       m_blue (b)
+    {
+    }
+
+    bool operator== (const color &other) const
+    {
+      if (m_simple != other.m_simple)
+       return false;
+      if (m_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
+    {
+      if (m_simple != other.m_simple)
+       return m_simple < other.m_simple;
+      if (m_simple)
+       return m_value < other.m_value;
+      if (m_red < other.m_red)
+       return true;
+      if (m_red == other.m_red)
+       {
+         if (m_green < other.m_green)
+           return true;
+         if (m_green == other.m_green)
+           return m_blue < other.m_blue;
+       }
+      return false;
+    }
+
+    /* Return true if this is the "NONE" color, false otherwise.  */
+    bool is_none () const
+    {
+      return m_simple && m_value == NONE;
+    }
+
+    /* Return true if this is one of the basic colors, false
+       otherwise.  */
+    bool is_basic () const
+    {
+      return m_simple && m_value >= BLACK && m_value <= WHITE;
+    }
+
+    /* Return the value of a basic color.  */
+    int get_value () const
+    {
+      gdb_assert (is_basic ());
+      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.  Returns true if any characters were written; returns
+       false otherwise (which can only happen for the "NONE"
+       color).  */
+    bool append_ansi (bool is_fg, std::string *str) const;
+
+  private:
+
+    bool m_simple;
+    int m_value;
+    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);
+  }
+
+  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 specified reverse display; false
+     otherwise.  */
+  bool is_reverse () const
+  {
+    return m_reverse;
+  }
+
+  /* Return the foreground color of this style.  */
+  const color &get_foreground () const
+  {
+    return m_foreground;
+  }
+
+  /* Return the background color of this style.  */
+  const color &get_background () const
+  {
+    return m_background;
+  }
+
+  /* Return the intensity of this style.  */
+  intensity get_intensity () const
+  {
+    return m_intensity;
+  }
+
+  /* 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);
+
+private:
+
+  color m_foreground = NONE;
+  color m_background = NONE;
+  intensity m_intensity = NORMAL;
+  bool m_reverse = false;
+};
+
+/* 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.  In either case, N_READ is updated to
+   reflect the number of chars read from BUF.  */
+
+extern bool skip_ansi_escape (const char *buf, int *n_read);
+
+#endif /* UI_STYLE_H */
diff --git a/gdb/unittests/style-selftests.c b/gdb/unittests/style-selftests.c
new file mode 100644 (file)
index 0000000..108ed6c
--- /dev/null
@@ -0,0 +1,109 @@
+/* Self tests for ui_file_style
+
+   Copyright (C) 2018 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 "defs.h"
+#include "selftest.h"
+#include "ui-style.h"
+
+namespace selftests {
+namespace style {
+
+#define CHECK_RGB(R, G, B) \
+  SELF_CHECK (rgb[0] == (R) && rgb[1] == (G) && rgb[2] == (B))
+
+static void
+run_tests ()
+{
+  ui_file_style style;
+  size_t n_read;
+  uint8_t rgb[3];
+
+  SELF_CHECK (style.parse ("\033[m", &n_read));
+  SELF_CHECK (n_read == 3);
+  SELF_CHECK (style.get_foreground ().is_none ());
+  SELF_CHECK (style.get_background ().is_none ());
+  SELF_CHECK (style.get_intensity () == ui_file_style::NORMAL);
+  SELF_CHECK (!style.is_reverse ());
+  SELF_CHECK (style.to_ansi () == "\033[m");
+
+  style = ui_file_style ();
+  SELF_CHECK (style.parse ("\033[0m", &n_read));
+  SELF_CHECK (n_read == 4);
+  SELF_CHECK (style.get_foreground ().is_none ());
+  SELF_CHECK (style.get_background ().is_none ());
+  SELF_CHECK (style.get_intensity () == ui_file_style::NORMAL);
+  SELF_CHECK (!style.is_reverse ());
+  /* This particular case does not round-trip identically, but the
+     difference is unimportant.  */
+  SELF_CHECK (style.to_ansi () == "\033[m");
+
+  SELF_CHECK (style.parse ("\033[7m", &n_read));
+  SELF_CHECK (n_read == 4);
+  SELF_CHECK (style.get_foreground ().is_none ());
+  SELF_CHECK (style.get_background ().is_none ());
+  SELF_CHECK (style.get_intensity () == ui_file_style::NORMAL);
+  SELF_CHECK (style.is_reverse ());
+  SELF_CHECK (style.to_ansi () == "\033[7m");
+
+  style = ui_file_style ();
+  SELF_CHECK (style.parse ("\033[32;1m", &n_read));
+  SELF_CHECK (n_read == 7);
+  SELF_CHECK (style.get_foreground ().is_basic ());
+  SELF_CHECK (style.get_foreground ().get_value () == ui_file_style::GREEN);
+  SELF_CHECK (style.get_background ().is_none ());
+  SELF_CHECK (style.get_intensity () == ui_file_style::BOLD);
+  SELF_CHECK (!style.is_reverse ());
+  SELF_CHECK (style.to_ansi () == "\033[32;1m");
+
+  style = ui_file_style ();
+  SELF_CHECK (style.parse ("\033[38;5;112;48;5;249m", &n_read));
+  SELF_CHECK (n_read == 20);
+  SELF_CHECK (!style.get_foreground ().is_basic ());
+  style.get_foreground ().get_rgb (rgb);
+  CHECK_RGB (0x87, 0xd7, 0);
+  SELF_CHECK (!style.get_background ().is_basic ());
+  style.get_background ().get_rgb (rgb);
+  CHECK_RGB (0xb2, 0xb2, 0xb2);
+  SELF_CHECK (style.get_intensity () == ui_file_style::NORMAL);
+  SELF_CHECK (!style.is_reverse ());
+  SELF_CHECK (style.to_ansi () == "\033[38;5;112;48;5;249m");
+
+  style = ui_file_style ();
+  SELF_CHECK (style.parse ("\033[38;2;83;84;85;48;2;0;1;254;2;7m", &n_read));
+  SELF_CHECK (n_read == 33);
+  SELF_CHECK (!style.get_foreground ().is_basic ());
+  style.get_foreground ().get_rgb (rgb);
+  CHECK_RGB (83, 84, 85);
+  SELF_CHECK (!style.get_background ().is_basic ());
+  style.get_background ().get_rgb (rgb);
+  CHECK_RGB (0, 1, 254);
+  SELF_CHECK (style.get_intensity () == ui_file_style::DIM);
+  SELF_CHECK (style.is_reverse ());
+  SELF_CHECK (style.to_ansi () == "\033[38;2;83;84;85;48;2;0;1;254;2;7m");
+}
+
+} /* namespace style */
+} /* namespace selftests */
+
+void
+_initialize_style_selftest ()
+{
+  selftests::register_test ("style",
+                           selftests::style::run_tests);
+}