#include #include #include #include #include "config.h" #include #include #ifdef HAVE_GIO_UNIX #include #include #endif /* This is the guts of gtk_text_buffer_insert_markup, * copied here so we can make an incremental version. */ static void insert_tags_for_attributes (GtkTextBuffer *buffer, PangoAttrIterator *iter, GtkTextIter *start, GtkTextIter *end) { GtkTextTagTable *table; GSList *attrs, *l; GtkTextTag *tag; char name[256]; float fg_alpha, bg_alpha; table = gtk_text_buffer_get_tag_table (buffer); #define LANGUAGE_ATTR(attr_name) \ { \ const char *language = pango_language_to_string (((PangoAttrLanguage*)attr)->value); \ g_snprintf (name, 256, "language=%s", language); \ tag = gtk_text_tag_table_lookup (table, name); \ if (!tag) \ { \ tag = gtk_text_tag_new (name); \ g_object_set (tag, #attr_name, language, NULL); \ gtk_text_tag_table_add (table, tag); \ g_object_unref (tag); \ } \ gtk_text_buffer_apply_tag (buffer, tag, start, end); \ } #define STRING_ATTR(attr_name) \ { \ const char *string = ((PangoAttrString*)attr)->value; \ g_snprintf (name, 256, #attr_name "=%s", string); \ tag = gtk_text_tag_table_lookup (table, name); \ if (!tag) \ { \ tag = gtk_text_tag_new (name); \ g_object_set (tag, #attr_name, string, NULL); \ gtk_text_tag_table_add (table, tag); \ g_object_unref (tag); \ } \ gtk_text_buffer_apply_tag (buffer, tag, start, end); \ } #define INT_ATTR(attr_name) \ { \ int value = ((PangoAttrInt*)attr)->value; \ g_snprintf (name, 256, #attr_name "=%d", value); \ tag = gtk_text_tag_table_lookup (table, name); \ if (!tag) \ { \ tag = gtk_text_tag_new (name); \ g_object_set (tag, #attr_name, value, NULL); \ gtk_text_tag_table_add (table, tag); \ g_object_unref (tag); \ } \ gtk_text_buffer_apply_tag (buffer, tag, start, end); \ } #define FONT_ATTR(attr_name) \ { \ PangoFontDescription *desc = ((PangoAttrFontDesc*)attr)->desc; \ char *str = pango_font_description_to_string (desc); \ g_snprintf (name, 256, "font-desc=%s", str); \ g_free (str); \ tag = gtk_text_tag_table_lookup (table, name); \ if (!tag) \ { \ tag = gtk_text_tag_new (name); \ g_object_set (tag, #attr_name, desc, NULL); \ gtk_text_tag_table_add (table, tag); \ g_object_unref (tag); \ } \ gtk_text_buffer_apply_tag (buffer, tag, start, end); \ } #define FLOAT_ATTR(attr_name) \ { \ float value = ((PangoAttrFloat*)attr)->value; \ g_snprintf (name, 256, #attr_name "=%g", value); \ tag = gtk_text_tag_table_lookup (table, name); \ if (!tag) \ { \ tag = gtk_text_tag_new (name); \ g_object_set (tag, #attr_name, value, NULL); \ gtk_text_tag_table_add (table, tag); \ g_object_unref (tag); \ } \ gtk_text_buffer_apply_tag (buffer, tag, start, end); \ } #define RGBA_ATTR(attr_name, alpha_value) \ { \ PangoColor *color; \ GdkRGBA rgba; \ color = &((PangoAttrColor*)attr)->color; \ rgba.red = color->red / 65535.; \ rgba.green = color->green / 65535.; \ rgba.blue = color->blue / 65535.; \ rgba.alpha = alpha_value; \ char *str = gdk_rgba_to_string (&rgba); \ g_snprintf (name, 256, #attr_name "=%s", str); \ g_free (str); \ tag = gtk_text_tag_table_lookup (table, name); \ if (!tag) \ { \ tag = gtk_text_tag_new (name); \ g_object_set (tag, #attr_name, &rgba, NULL); \ gtk_text_tag_table_add (table, tag); \ g_object_unref (tag); \ } \ gtk_text_buffer_apply_tag (buffer, tag, start, end); \ } fg_alpha = bg_alpha = 1.; attrs = pango_attr_iterator_get_attrs (iter); for (l = attrs; l; l = l->next) { PangoAttribute *attr = l->data; switch ((int)attr->klass->type) { case PANGO_ATTR_FOREGROUND_ALPHA: fg_alpha = ((PangoAttrInt*)attr)->value / 65535.; break; case PANGO_ATTR_BACKGROUND_ALPHA: bg_alpha = ((PangoAttrInt*)attr)->value / 65535.; break; default: break; } } for (l = attrs; l; l = l->next) { PangoAttribute *attr = l->data; switch (attr->klass->type) { case PANGO_ATTR_LANGUAGE: LANGUAGE_ATTR (language); break; case PANGO_ATTR_FAMILY: STRING_ATTR (family); break; case PANGO_ATTR_STYLE: INT_ATTR (style); break; case PANGO_ATTR_WEIGHT: INT_ATTR (weight); break; case PANGO_ATTR_VARIANT: INT_ATTR (variant); break; case PANGO_ATTR_STRETCH: INT_ATTR (stretch); break; case PANGO_ATTR_SIZE: INT_ATTR (size); break; case PANGO_ATTR_FONT_DESC: FONT_ATTR (font-desc); break; case PANGO_ATTR_FOREGROUND: RGBA_ATTR (foreground_rgba, fg_alpha); break; case PANGO_ATTR_BACKGROUND: RGBA_ATTR (background_rgba, bg_alpha); break; case PANGO_ATTR_UNDERLINE: INT_ATTR (underline); break; case PANGO_ATTR_UNDERLINE_COLOR: RGBA_ATTR (underline_rgba, fg_alpha); break; case PANGO_ATTR_OVERLINE: INT_ATTR (overline); break; case PANGO_ATTR_OVERLINE_COLOR: RGBA_ATTR (overline_rgba, fg_alpha); break; case PANGO_ATTR_STRIKETHROUGH: INT_ATTR (strikethrough); break; case PANGO_ATTR_STRIKETHROUGH_COLOR: RGBA_ATTR (strikethrough_rgba, fg_alpha); break; case PANGO_ATTR_RISE: INT_ATTR (rise); break; case PANGO_ATTR_SCALE: FLOAT_ATTR (scale); break; case PANGO_ATTR_FALLBACK: INT_ATTR (fallback); break; case PANGO_ATTR_LETTER_SPACING: INT_ATTR (letter_spacing); break; case PANGO_ATTR_FONT_FEATURES: STRING_ATTR (font_features); break; case PANGO_ATTR_ALLOW_BREAKS: INT_ATTR (allow_breaks); break; case PANGO_ATTR_SHOW: INT_ATTR (show_spaces); break; case PANGO_ATTR_INSERT_HYPHENS: INT_ATTR (insert_hyphens); break; #if PANGO_VERSION_CHECK(1,49,0) case PANGO_ATTR_LINE_HEIGHT: case PANGO_ATTR_ABSOLUTE_LINE_HEIGHT: break; #endif case PANGO_ATTR_SHAPE: case PANGO_ATTR_ABSOLUTE_SIZE: case PANGO_ATTR_GRAVITY: case PANGO_ATTR_GRAVITY_HINT: case PANGO_ATTR_FOREGROUND_ALPHA: case PANGO_ATTR_BACKGROUND_ALPHA: break; case PANGO_ATTR_INVALID: default: g_assert_not_reached (); break; } } g_slist_free_full (attrs, (GDestroyNotify)pango_attribute_destroy); #undef LANGUAGE_ATTR #undef STRING_ATTR #undef INT_ATTR #undef FONT_ATTR #undef FLOAT_ATTR #undef RGBA_ATTR } typedef struct { GMarkupParseContext *parser; char *markup; gsize pos; gsize len; GtkTextBuffer *buffer; GtkTextIter iter; GtkTextMark *mark; PangoAttrList *attributes; char *text; PangoAttrIterator *attr; } MarkupData; static void free_markup_data (MarkupData *mdata) { g_free (mdata->markup); g_clear_pointer (&mdata->parser, g_markup_parse_context_free); gtk_text_buffer_delete_mark (mdata->buffer, mdata->mark); g_clear_pointer (&mdata->attr, pango_attr_iterator_destroy); g_clear_pointer (&mdata->attributes, pango_attr_list_unref); g_free (mdata->text); g_object_unref (mdata->buffer); g_free (mdata); } static gboolean insert_markup_idle (gpointer data) { MarkupData *mdata = data; gint64 begin; begin = g_get_monotonic_time (); do { int start, end; int start_offset; GtkTextIter start_iter; if (g_get_monotonic_time () - begin > G_TIME_SPAN_MILLISECOND) { g_idle_add (insert_markup_idle, data); return G_SOURCE_REMOVE; } pango_attr_iterator_range (mdata->attr, &start, &end); if (end == G_MAXINT) /* last chunk */ end = start - 1; /* resulting in -1 to be passed to _insert */ start_offset = gtk_text_iter_get_offset (&mdata->iter); gtk_text_buffer_insert (mdata->buffer, &mdata->iter, mdata->text + start, end - start); gtk_text_buffer_get_iter_at_offset (mdata->buffer, &start_iter, start_offset); insert_tags_for_attributes (mdata->buffer, mdata->attr, &start_iter, &mdata->iter); gtk_text_buffer_get_iter_at_mark (mdata->buffer, &mdata->iter, mdata->mark); } while (pango_attr_iterator_next (mdata->attr)); free_markup_data (mdata); return G_SOURCE_REMOVE; } static gboolean parse_markup_idle (gpointer data) { MarkupData *mdata = data; gint64 begin; GError *error = NULL; begin = g_get_monotonic_time (); do { if (g_get_monotonic_time () - begin > G_TIME_SPAN_MILLISECOND) { g_idle_add (parse_markup_idle, data); return G_SOURCE_REMOVE; } if (!g_markup_parse_context_parse (mdata->parser, mdata->markup + mdata->pos, MIN (4096, mdata->len - mdata->pos), &error)) { g_warning ("Invalid markup string: %s", error->message); g_error_free (error); free_markup_data (mdata); return G_SOURCE_REMOVE; } mdata->pos += 4096; } while (mdata->pos < mdata->len); if (!pango_markup_parser_finish (mdata->parser, &mdata->attributes, &mdata->text, NULL, &error)) { g_warning ("Invalid markup string: %s", error->message); g_error_free (error); free_markup_data (mdata); return G_SOURCE_REMOVE; } if (!mdata->attributes) { gtk_text_buffer_insert (mdata->buffer, &mdata->iter, mdata->text, -1); free_markup_data (mdata); return G_SOURCE_REMOVE; } mdata->attr = pango_attr_list_get_iterator (mdata->attributes); insert_markup_idle (data); return G_SOURCE_REMOVE; } /* Takes a ref on @buffer while it is operating, * and consumes @markup. */ static void insert_markup (GtkTextBuffer *buffer, GtkTextIter *iter, char *markup, int len) { MarkupData *data; g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer)); data = g_new0 (MarkupData, 1); data->buffer = g_object_ref (buffer); data->iter = *iter; data->markup = markup; data->len = len; data->parser = pango_markup_parser_new (0); data->pos = 0; /* create mark with right gravity */ data->mark = gtk_text_buffer_create_mark (buffer, NULL, iter, FALSE); parse_markup_idle (data); } static void fontify_finish (GObject *source, GAsyncResult *result, gpointer data) { GSubprocess *subprocess = G_SUBPROCESS (source); GtkTextBuffer *buffer = data; GBytes *stdout_buf = NULL; GBytes *stderr_buf = NULL; GError *error = NULL; if (!g_subprocess_communicate_finish (subprocess, result, &stdout_buf, &stderr_buf, &error)) { g_clear_pointer (&stdout_buf, g_bytes_unref); g_clear_pointer (&stderr_buf, g_bytes_unref); g_warning ("%s", error->message); g_clear_error (&error); g_object_unref (subprocess); g_object_unref (buffer); return; } if (g_subprocess_get_exit_status (subprocess) != 0) { if (stderr_buf) g_warning ("%s", (char *)g_bytes_get_data (stderr_buf, NULL)); g_clear_pointer (&stderr_buf, g_bytes_unref); } g_object_unref (subprocess); g_clear_pointer (&stderr_buf, g_bytes_unref); if (stdout_buf) { char *markup; gsize len; char *p; GtkTextIter start; gtk_text_buffer_set_text (buffer, "", 0); /* highlight puts a span with font and size around its output, * which we don't want. */ markup = g_bytes_unref_to_data (stdout_buf, &len); for (p = markup + strlen ("message); g_clear_error (&error); return; } gtk_text_buffer_get_bounds (source_buffer, &start, &end); text = gtk_text_buffer_get_text (source_buffer, &start, &end, TRUE); bytes = g_bytes_new_take (text, strlen (text)); #ifdef HAVE_GIO_UNIX /* Work around https://gitlab.gnome.org/GNOME/glib/-/issues/2182 */ if (G_IS_UNIX_OUTPUT_STREAM (g_subprocess_get_stdin_pipe (subprocess))) { GOutputStream *stdin_pipe = g_subprocess_get_stdin_pipe (subprocess); int fd = g_unix_output_stream_get_fd (G_UNIX_OUTPUT_STREAM (stdin_pipe)); fcntl (fd, F_SETFL, O_NONBLOCK); } #endif g_subprocess_communicate_async (subprocess, bytes, NULL, fontify_finish, g_object_ref (source_buffer)); g_bytes_unref (bytes); }