Commit 56d91c47 authored by Sébastien Lafargue's avatar Sébastien Lafargue
Browse files

build-output: handle VT color codes

parent 83f40331
......@@ -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);
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment