[gnome-builder] build-output: handle VT color codes



commit 56d91c475f5b693e745fbe3b7154a49ae6495273
Author: Sebastien Lafargue <slafargue gnome org>
Date:   Tue Feb 14 19:11:49 2017 +0100

    build-output: handle VT color codes

 libide/buildui/ide-build-log-panel.c |  465 ++++++++++++++++++++++++++++++++--
 1 files changed, 447 insertions(+), 18 deletions(-)
---
diff --git a/libide/buildui/ide-build-log-panel.c b/libide/buildui/ide-build-log-panel.c
index 21a367e..ad6ea8b 100644
--- a/libide/buildui/ide-build-log-panel.c
+++ b/libide/buildui/ide-build-log-panel.c
@@ -27,6 +27,19 @@
 
 #include "ide-build-log-panel.h"
 
+typedef struct _ColorCodeState
+{
+  /* A value of -1 is used to specify a default foreground orr background */
+  gint16 foreground;
+  gint16 background;
+
+  guint  bold       : 1;
+  guint  dim        : 1;
+  guint  underlined : 1;
+  guint  reverse    : 1;
+  guint  hidden     : 1;
+} ColorCodeState;
+
 struct _IdeBuildLogPanel
 {
   PnlDockWidget      parent_instance;
@@ -38,7 +51,14 @@ struct _IdeBuildLogPanel
 
   GtkScrolledWindow *scroller;
   GtkTextView       *text_view;
+
   GtkTextTag        *stderr_tag;
+  GPtrArray         *color_codes_foreground_tags;
+  GPtrArray         *color_codes_background_tags;
+  GtkTextTag        *color_codes_bold_tag;
+  GtkTextTag        *color_codes_underlined_tag;
+  ColorCodeState     color_codes_state;
+  ColorCodeState     current_color_codes_state;
 
   guint              log_observer;
 };
@@ -53,6 +73,421 @@ G_DEFINE_TYPE (IdeBuildLogPanel, ide_build_log_panel, PNL_TYPE_DOCK_WIDGET)
 
 static GParamSpec *properties [LAST_PROP];
 
+/* TODO: Same hard-coded palette as terminal-view
+ * till we have code for custom palettes
+ */
+#define COLOR_PALETTE_NB_COLORS 16
+
+static const GdkRGBA solarized_palette[] =
+{
+  /*
+   * Solarized palette (1.0.0beta2):
+   * http://ethanschoonover.com/solarized
+   */
+  { 0.02745,  0.211764, 0.258823, 1 },
+  { 0.862745, 0.196078, 0.184313, 1 },
+  { 0.521568, 0.6,      0,        1 },
+  { 0.709803, 0.537254, 0,        1 },
+  { 0.149019, 0.545098, 0.823529, 1 },
+  { 0.82745,  0.211764, 0.509803, 1 },
+  { 0.164705, 0.631372, 0.596078, 1 },
+  { 0.933333, 0.909803, 0.835294, 1 },
+  { 0,        0.168627, 0.211764, 1 },
+  { 0.796078, 0.294117, 0.086274, 1 },
+  { 0.345098, 0.431372, 0.458823, 1 },
+  { 0.396078, 0.482352, 0.513725, 1 },
+  { 0.513725, 0.580392, 0.588235, 1 },
+  { 0.423529, 0.443137, 0.768627, 1 },
+  { 0.57647,  0.631372, 0.631372, 1 },
+  { 0.992156, 0.964705, 0.890196, 1 },
+};
+
+typedef enum
+{
+  COLOR_CODE_NONE,
+  COLOR_CODE_TAG,
+  COLOR_CODE_INVALID,
+  COLOR_CODE_SKIP,
+} ColorCodeType;
+
+static void
+init_color_tags_from_palette (IdeBuildLogPanel *self)
+{
+  GtkTextTag *tag;
+  GdkRGBA rgba;
+
+  g_assert (IDE_IS_BUILD_LOG_PANEL (self));
+  g_assert (self->buffer != NULL);
+
+  g_clear_pointer (&self->color_codes_foreground_tags, g_ptr_array_unref);
+  g_clear_pointer (&self->color_codes_background_tags, g_ptr_array_unref);
+  g_clear_object (&self->color_codes_bold_tag);
+  g_clear_object (&self->color_codes_underlined_tag);
+
+  self->color_codes_foreground_tags = g_ptr_array_new ();
+  for (gint i = 0; i < COLOR_PALETTE_NB_COLORS; ++i)
+    {
+      rgba = solarized_palette [i];
+      tag = gtk_text_buffer_create_tag (self->buffer, NULL, "foreground-rgba", &rgba, NULL);
+      g_ptr_array_add (self->color_codes_foreground_tags, tag);
+    }
+
+  self->color_codes_background_tags = g_ptr_array_new ();
+  for (gint i = 0; i < COLOR_PALETTE_NB_COLORS; ++i)
+    {
+      rgba = solarized_palette [i];
+      tag = gtk_text_buffer_create_tag (self->buffer, NULL, "background-rgba", &rgba, NULL);
+      g_ptr_array_add (self->color_codes_background_tags, tag);
+    }
+
+  self->color_codes_bold_tag =
+    g_object_ref (gtk_text_buffer_create_tag (self->buffer, NULL,
+                                              "weight", PANGO_WEIGHT_BOLD,
+                                              NULL));
+  self->color_codes_underlined_tag =
+    g_object_ref (gtk_text_buffer_create_tag (self->buffer, NULL,
+                                              "underline", PANGO_UNDERLINE_SINGLE,
+                                              NULL));
+}
+
+static inline gboolean
+is_foreground_color_value (gint value)
+{
+  return ((value >= 30 && value <= 37) || (value >= 90 && value <= 97));
+}
+
+static inline gboolean
+is_background_color_value (gint value)
+{
+  return ((value >= 40 && value <= 47) || (value >= 100 && value <= 107));
+}
+
+static inline gboolean
+is_format_color_value (gint value)
+{
+  return (value == 1 || value == 2 || value == 4 || value == 5 || value == 7 || value == 8);
+}
+
+static inline gboolean
+is_reset_format_color_value (gint value)
+{
+  return (value == 21 || value == 22 || value == 24 || value == 25 || value == 27 || value == 28);
+}
+
+static inline gboolean
+is_reset_all_color_value (gint value)
+{
+  return (value == 0);
+}
+
+/* Return -1 if not valid.
+ * Cursor is updated in every cases.
+ */
+static gint
+str_to_int (const gchar **cursor_ptr)
+{
+  gint value = 0;
+
+  g_assert (cursor_ptr != NULL && *cursor_ptr != NULL);
+
+  if (**cursor_ptr == 'm')
+    return 0;
+
+  while (**cursor_ptr >= '0' && **cursor_ptr <= '9')
+    {
+      value *= 10;
+      value += **cursor_ptr - '0';
+
+      ++(*cursor_ptr);
+    }
+
+  if (is_foreground_color_value (value) ||
+      is_background_color_value (value) ||
+      is_format_color_value (value) ||
+      is_reset_format_color_value (value) ||
+      value == 0 || value == 39 || value == 49)
+    return value;
+  else
+    return -1;
+}
+
+static gint
+color_code_value_to_tag_index (gint value)
+{
+  if (value >=30 && value <= 37)
+    return value - 30;
+
+  if (value >=90 && value <= 97)
+    return value - 82;
+
+  return -1;
+}
+
+static void
+color_codes_state_reset (ColorCodeState *color_codes_state)
+{
+  g_assert (color_codes_state != NULL);
+
+  color_codes_state->foreground = -1;
+  color_codes_state->background = -1;
+
+  color_codes_state->bold = FALSE;
+  color_codes_state->dim = FALSE;
+  color_codes_state->reverse = FALSE;
+  color_codes_state->underlined = FALSE;
+  color_codes_state->hidden = FALSE;
+}
+
+static void
+color_codes_state_update (IdeBuildLogPanel *self,
+                          ColorCodeState   *color_codes_state,
+                          gint              value)
+{
+  g_assert (IDE_IS_BUILD_LOG_PANEL (self));
+  g_assert (color_codes_state != NULL);
+
+  if (value == 0)
+    color_codes_state_reset (color_codes_state);
+  else if (value == 39)
+    color_codes_state->foreground = -1;
+  else if (value == 49)
+    color_codes_state->background = -1;
+  else if (is_foreground_color_value (value))
+    color_codes_state->foreground = value;
+  else if (is_background_color_value (value))
+    color_codes_state->background = value;
+  else if (is_format_color_value (value))
+    {
+      if (value == 1)
+        color_codes_state->bold = TRUE;
+      else if (value == 4)
+        color_codes_state->underlined = TRUE;
+    }
+  else if (is_reset_format_color_value (value))
+    {
+      if (value == 21)
+        color_codes_state->bold = FALSE;
+      else if (value == 24)
+        color_codes_state->underlined = FALSE;
+    }
+}
+
+static void
+color_codes_state_apply (IdeBuildLogPanel *self,
+                         ColorCodeState   *color_codes_state,
+                         GtkTextIter      *begin,
+                         GtkTextIter      *end)
+{
+  GtkTextTag *tag;
+  gint tag_index;
+
+  g_assert (IDE_IS_BUILD_LOG_PANEL (self));
+  g_assert (color_codes_state != NULL);
+  g_assert (begin != NULL);
+  g_assert (end != NULL);
+
+  if (color_codes_state->foreground != -1)
+    {
+      tag_index = color_code_value_to_tag_index (color_codes_state->foreground);
+      g_assert (tag_index != -1);
+
+      tag = g_ptr_array_index (self->color_codes_foreground_tags, tag_index);
+      gtk_text_buffer_apply_tag (self->buffer, tag, begin, end);
+    }
+
+  if (color_codes_state->background != -1)
+    {
+      tag_index = color_code_value_to_tag_index (color_codes_state->background);
+      g_assert (tag_index != -1);
+
+      tag = g_ptr_array_index (self->color_codes_background_tags, tag_index);
+      gtk_text_buffer_apply_tag (self->buffer, tag, begin, end);
+    }
+
+  if (color_codes_state->bold ==  TRUE)
+    gtk_text_buffer_apply_tag (self->buffer, self->color_codes_bold_tag, begin, end);
+
+  if (color_codes_state->underlined ==  TRUE)
+    gtk_text_buffer_apply_tag (self->buffer, self->color_codes_underlined_tag, begin, end);
+}
+
+static ColorCodeType
+fetch_color_codes_tags (IdeBuildLogPanel  *self,
+                        const gchar      **cursor,
+                        ColorCodeState    *color_codes_state)
+{
+  gint value;
+  ColorCodeType ret = COLOR_CODE_NONE;
+  ColorCodeState tmp_color_codes_state = *color_codes_state;
+
+  g_assert (IDE_IS_BUILD_LOG_PANEL (self));
+  g_assert (cursor != NULL && *cursor != NULL);
+  g_assert (color_codes_state  != NULL);
+
+  while (**cursor != '\0')
+    {
+      value = str_to_int (cursor);
+      if (value != -1)
+        {
+          if (is_foreground_color_value (value) ||
+              is_background_color_value (value) ||
+              is_format_color_value (value) ||
+              is_reset_format_color_value (value) ||
+              is_reset_all_color_value (value))
+            {
+              color_codes_state_update (self, &tmp_color_codes_state, value);
+              ret = COLOR_CODE_TAG;
+            }
+        }
+      else if (ret == COLOR_CODE_NONE)
+        ret = COLOR_CODE_INVALID;
+
+      if (**cursor == 'm')
+      {
+        if (ret != COLOR_CODE_INVALID)
+          *color_codes_state = tmp_color_codes_state;
+
+        ++(*cursor);
+        return ret;
+      }
+
+      if (**cursor != ';')
+        break;
+
+      ++(*cursor);
+    }
+
+  return COLOR_CODE_INVALID;
+}
+
+/**
+ * find_color_code:
+ * @self: a #GbpBuildLogPanel
+ * @msg: text to search in
+ * @color_codes_state: (inout) : if a color code is found, the state is updated
+ * @start: (out) point to the first char of a found code
+ * @end: (out) point to the last char + 1 of a found code
+ *
+ * If no color code is found, start and end point to the string's end.
+ *
+ * Returns: a #ColorCodeType indicating the state of the search.
+ */
+
+static ColorCodeType
+find_color_code (IdeBuildLogPanel  *self,
+                 const gchar       *msg,
+                 ColorCodeState    *color_codes_state,
+                 const gchar      **start,
+                 const gchar      **end)
+{
+  const gchar *cursor = msg;
+  ColorCodeType ret;
+
+  g_assert (IDE_IS_BUILD_LOG_PANEL (self));
+  g_assert (!ide_str_empty0 (msg));
+  g_assert (color_codes_state != NULL);
+  g_assert (start != NULL);
+  g_assert (end != NULL);
+
+  while (*cursor != '\0')
+    {
+      if (*cursor == '\\' && *(cursor + 1) == 'e')
+        {
+          *start = cursor;
+          cursor += 2;
+        }
+      else if (*cursor == '\033' || *cursor == '\x01b')
+        {
+          *start = cursor;
+          ++cursor;
+        }
+      else
+        goto next;
+
+      if (*cursor == '[')
+        {
+          ++cursor;
+          if (*cursor == '\0')
+            goto end;
+
+          if (*cursor == 'K')
+            {
+              *end = cursor + 1;
+              return COLOR_CODE_SKIP;
+            }
+
+          ret = fetch_color_codes_tags (self, &cursor, color_codes_state);
+          *end = cursor;
+
+          return ret;
+        }
+
+      if (*cursor == '\0')
+        goto end;
+
+next:
+      /* TODO: skip a possible escaped char */
+      cursor = g_utf8_next_char (cursor);
+    }
+
+end:
+  *start = *end = cursor;
+  return COLOR_CODE_NONE;
+}
+
+/* Transform VT color codes into tags before inserting the text */
+static void
+ide_build_log_panel_insert_text (IdeBuildLogPanel  *self,
+                                 const gchar       *message,
+                                 GtkTextIter       *iter,
+                                 IdeBuildLogStream  stream)
+{
+  ColorCodeType tag_type;
+  ColorCodeType current_tag_type = COLOR_CODE_NONE;
+  GtkTextIter pos = *iter;
+  const gchar *cursor = message;
+  const gchar *tag_start;
+  const gchar *tag_end;
+  gsize len;
+
+  g_assert (IDE_IS_BUILD_LOG_PANEL (self));
+  g_assert (iter != NULL);
+
+  if (ide_str_empty0 (message))
+    return;
+
+  while (*cursor != '\0')
+    {
+      tag_type = find_color_code (self, cursor, &self->color_codes_state, &tag_start, &tag_end);
+      len = tag_start - cursor;
+      if (len > 0)
+        {
+          GtkTextIter begin;
+          guint offset;
+
+          offset = gtk_text_iter_get_offset (&pos);
+          gtk_text_buffer_insert (self->buffer, &pos, cursor, len);
+          gtk_text_buffer_get_iter_at_offset (self->buffer, &begin, offset);
+
+          if (current_tag_type == COLOR_CODE_TAG || current_tag_type == COLOR_CODE_SKIP)
+            color_codes_state_apply (self, &self->current_color_codes_state, &begin, &pos);
+
+          if (G_LIKELY (stream != IDE_BUILD_LOG_STDOUT))
+            gtk_text_buffer_apply_tag (self->buffer, self->stderr_tag, &begin, &pos);
+        }
+
+      current_tag_type = tag_type;
+      self->current_color_codes_state = self->color_codes_state;
+
+      if (tag_type == COLOR_CODE_NONE)
+        break;
+
+      cursor = tag_end;
+    }
+
+  gtk_text_buffer_insert (self->buffer, &pos, "\n", 1);
+}
+
 static void
 ide_build_log_panel_reset_view (IdeBuildLogPanel *self)
 {
@@ -69,9 +504,14 @@ ide_build_log_panel_reset_view (IdeBuildLogPanel *self)
   self->stderr_tag = gtk_text_buffer_create_tag (self->buffer,
                                                  "stderr-tag",
                                                  "foreground", "#ff0000",
-                                                 "weight", PANGO_WEIGHT_BOLD,
+                                                 "weight", PANGO_WEIGHT_NORMAL,
                                                  NULL);
 
+  /* We set VT color codes tags after stderr_tag so that they have higher priority */
+  init_color_tags_from_palette (self);
+  color_codes_state_reset (&self->current_color_codes_state);
+  color_codes_state_reset (&self->color_codes_state);
+
   self->text_view = g_object_new (GTK_TYPE_TEXT_VIEW,
                                   "bottom-margin", 3,
                                   "buffer", self->buffer,
@@ -105,23 +545,7 @@ ide_build_log_panel_log_observer (IdeBuildLogStream  stream,
   g_assert (message[message_len] == '\0');
 
   gtk_text_buffer_get_end_iter (self->buffer, &iter);
-
-  if G_LIKELY (stream == IDE_BUILD_LOG_STDOUT)
-    {
-      gtk_text_buffer_insert (self->buffer, &iter, message, -1);
-      gtk_text_buffer_insert (self->buffer, &iter, "\n", 1);
-    }
-  else
-    {
-      GtkTextIter begin;
-      guint offset;
-
-      offset = gtk_text_iter_get_offset (&iter);
-      gtk_text_buffer_insert (self->buffer, &iter, message, -1);
-      gtk_text_buffer_insert (self->buffer, &iter, "\n", 1);
-      gtk_text_buffer_get_iter_at_offset (self->buffer, &begin, offset);
-      gtk_text_buffer_apply_tag (self->buffer, self->stderr_tag, &begin, &iter);
-    }
+  ide_build_log_panel_insert_text (self, message, &iter, stream);
 
   insert = gtk_text_buffer_get_insert (self->buffer);
   gtk_text_view_scroll_to_mark (self->text_view, insert, 0.0, TRUE, 0.0, 0.0);
@@ -199,6 +623,11 @@ ide_build_log_panel_finalize (GObject *object)
   g_clear_object (&self->css);
   g_clear_object (&self->settings);
 
+  g_clear_pointer (&self->color_codes_foreground_tags, g_ptr_array_unref);
+  g_clear_pointer (&self->color_codes_background_tags, g_ptr_array_unref);
+  g_clear_object (&self->color_codes_bold_tag);
+  g_clear_object (&self->color_codes_underlined_tag);
+
   G_OBJECT_CLASS (ide_build_log_panel_parent_class)->finalize (object);
 }
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]