static void insert_recursive(GtkTextBuffer *buffer, GtkTextIter *iter, const char *text, GSList *tags) { if (!text || !*text) return; const char *p = text; while (*p) { // Strikethrough ~~ if (strncmp(p, "~~", 2) == 0) { const char *end = strstr(p + 2, "~~"); if (end) { char *inner = g_strndup(p + 2, end - p - 2); GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "strikethrough"); GSList *new_tags = g_slist_prepend(g_slist_copy(tags), tag); insert_recursive(buffer, iter, inner, new_tags); g_slist_free(new_tags); g_free(inner); p = end + 2; continue; } } // Bold+Italic *** if (strncmp(p, "***", 3) == 0) { const char *end = strstr(p + 3, "***"); if (end) { char *inner = g_strndup(p + 3, end - p - 3); GtkTextTag *t1 = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "bold"); GtkTextTag *t2 = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "italic"); GSList *new_tags = g_slist_prepend(g_slist_prepend(g_slist_copy(tags), t1), t2); insert_recursive(buffer, iter, inner, new_tags); g_slist_free(new_tags); g_free(inner); p = end + 3; continue; } } // Bold ** or __ if (strncmp(p, "**", 2) == 0 || strncmp(p, "__", 2) == 0) { const char *marker = strncmp(p, "**", 2) == 0 ? "**" : "__"; const char *end = strstr(p + 2, marker); if (end) { char *inner = g_strndup(p + 2, end - p - 2); GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "bold"); GSList *new_tags = g_slist_prepend(g_slist_copy(tags), tag); insert_recursive(buffer, iter, inner, new_tags); g_slist_free(new_tags); g_free(inner); p = end + 2; continue; } } // Italic * or _ if (*p == '*' || *p == '_') { char marker[2] = {*p, 0}; const char *end = strpbrk(p + 1, marker); if (end && *end == *p) { char *inner = g_strndup(p + 1, end - p - 1); GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "italic"); GSList *new_tags = g_slist_prepend(g_slist_copy(tags), tag); insert_recursive(buffer, iter, inner, new_tags); g_slist_free(new_tags); g_free(inner); p = end + 1; continue; } } // Code ` if (*p == '`') { const char *end = strchr(p + 1, '`'); if (end) { char *inner = g_strndup(p + 1, end - p - 1); GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "code"); GSList *new_tags = g_slist_prepend(g_slist_copy(tags), tag); // Code is not recursive GtkTextIter start_ins = *iter; gtk_text_buffer_insert(buffer, iter, inner, -1); for (GSList *l = new_tags; l; l = l->next) { gtk_text_buffer_apply_tag(buffer, (GtkTextTag*)l->data, &start_ins, iter); } g_slist_free(new_tags); g_free(inner); p = end + 1; continue; } } // Image ![alt](url) if (strncmp(p, "![", 2) == 0) { const char *alt_end = strchr(p + 2, ']'); if (alt_end && alt_end[1] == '(') { const char *url_end = strchr(alt_end + 2, ')'); if (url_end) { char *path_start = (char*)alt_end + 2; char *path = g_strndup(path_start, url_end - path_start); if (strncmp(path, "http", 4) == 0) { // Placeholder for remote char *msg = g_strdup_printf("[Remote Image: %s]", path); GtkTextIter start_ins = *iter; gtk_text_buffer_insert(buffer, iter, msg, -1); g_free(msg); } else { GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file_at_scale(path, 600, -1, TRUE, NULL); if (pixbuf) { gtk_text_buffer_insert_pixbuf(buffer, iter, pixbuf); g_object_unref(pixbuf); } else { char *msg = g_strdup_printf("[Image not found: %s]", path); gtk_text_buffer_insert(buffer, iter, msg, -1); g_free(msg); } } g_free(path); p = url_end + 1; continue; } } } // Link [text](url) if (*p == '[') { const char *txt_end = strchr(p + 1, ']'); if (txt_end && txt_end[1] == '(') { const char *url_end = strchr(txt_end + 2, ')'); if (url_end) { char *txt = g_strndup(p + 1, txt_end - p - 1); char *url = g_strndup(txt_end + 2, url_end - txt_end - 2); GtkTextTag *url_tag = gtk_text_buffer_create_tag(buffer, NULL, "foreground", "blue", "underline", PANGO_UNDERLINE_SINGLE, NULL); g_object_set_data_full(G_OBJECT(url_tag), "url", g_strdup(url), g_free); GSList *new_tags = g_slist_prepend(g_slist_copy(tags), url_tag); insert_recursive(buffer, iter, txt, new_tags); g_slist_free(new_tags); g_free(txt); g_free(url); p = url_end + 1; continue; } } } // Auto-link http://... if (strncmp(p, "http://", 7) == 0 || strncmp(p, "https://", 8) == 0) { const char *end = p; while (*end && !isspace(*end) && *end != ')' && *end != ']' && *end != '>') end++; char *url = g_strndup(p, end - p); GtkTextTag *url_tag = gtk_text_buffer_create_tag(buffer, NULL, "foreground", "blue", "underline", PANGO_UNDERLINE_SINGLE, NULL); g_object_set_data_full(G_OBJECT(url_tag), "url", g_strdup(url), g_free); GtkTextIter start_ins = *iter; gtk_text_buffer_insert(buffer, iter, url, -1); // Apply background tags + url tag for (GSList *l = tags; l; l = l->next) gtk_text_buffer_apply_tag(buffer, (GtkTextTag*)l->data, &start_ins, iter); gtk_text_buffer_apply_tag(buffer, url_tag, &start_ins, iter); g_free(url); p = end; continue; } // Plain text GtkTextIter start_ins = *iter; char buf[2] = {*p, 0}; gtk_text_buffer_insert(buffer, iter, buf, 1); for (GSList *l = tags; l; l = l->next) { gtk_text_buffer_apply_tag(buffer, (GtkTextTag*)l->data, &start_ins, iter); } p++; } }