/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4; coding: utf-8 -*- * * mooeditlang.c * * Copyright (C) 2004-2005 by Yevgen Muntyan * * 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 2 of the License, or * (at your option) any later version. * * See COPYING file that comes with this distribution. */ #ifdef HAVE_CONFIG_H #include #endif #define MOOEDIT_COMPILATION #include "mooedit/mooeditlang-private.h" #include "mooedit/mooeditprefs.h" #include "mooutils/moocompat.h" #include static void moo_edit_lang_class_init (MooEditLangClass *klass); static void moo_edit_lang_init (MooEditLang *lang); static void moo_edit_lang_finalize (GObject *object); static void moo_edit_lang_write_style_setting (MooEditLang *lang, const char *style_id, const GtkSourceTagStyle *style); /* frees GPtrArray */ static void free_tag_array (GPtrArray *array); static void remove_tag (GPtrArray *array, GObject *tag); static void remove_weak_ref (const char *style_id, GPtrArray *tags_array); static GSList *copy_string_slist (const GSList *list); static void free_string_slist (GSList *list); static void add_string_pair (const char *id, const char *name, GPtrArray *array); /* MOO_TYPE_EDIT_LANG */ G_DEFINE_TYPE (MooEditLang, moo_edit_lang, G_TYPE_OBJECT) static void moo_edit_lang_class_init (MooEditLangClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = moo_edit_lang_finalize; } static void moo_edit_lang_init (MooEditLang *lang) { lang->priv = g_new0 (MooEditLangPrivate, 1); lang->priv->tag_id_to_style_id = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); lang->priv->style_id_to_tags = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)free_tag_array); lang->priv->style_id_to_style = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)gtk_source_tag_style_free); lang->priv->style_id_to_style_name = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); } static void moo_edit_lang_finalize (GObject *object) { MooEditLang *lang = MOO_EDIT_LANG (object); g_free (lang->priv->id); g_free (lang->priv->name); g_free (lang->priv->section); g_free (lang->priv->author); g_free (lang->priv->filename); free_string_slist (lang->priv->mime_types); free_string_slist (lang->priv->extensions); g_free (lang->priv->brackets); g_free (lang->priv->word_chars); g_hash_table_destroy (lang->priv->tag_id_to_style_id); g_hash_table_destroy (lang->priv->style_id_to_style); g_hash_table_destroy (lang->priv->style_id_to_style_name); g_hash_table_foreach (lang->priv->style_id_to_tags, (GHFunc) remove_weak_ref, NULL); g_hash_table_destroy (lang->priv->style_id_to_tags); g_slist_foreach (lang->priv->tags, (GFunc) g_object_unref, NULL); g_slist_free (lang->priv->tags); g_free (lang->priv); lang->priv = NULL; } MooEditLang *moo_edit_lang_new (const char *id) { MooEditLang *lang; g_return_val_if_fail (id != NULL, NULL); lang = MOO_EDIT_LANG (g_object_new (MOO_TYPE_EDIT_LANG, NULL)); lang->priv->id = g_strdup (id); lang->priv->loaded = TRUE; return lang; } MooEditLang *moo_edit_lang_new_from_file (const char *filename) { MooEditLang *lang; g_return_val_if_fail (filename != NULL, NULL); lang = MOO_EDIT_LANG (g_object_new (MOO_TYPE_EDIT_LANG, NULL)); lang->priv->filename = g_strdup (filename); if (!moo_edit_lang_load_description (lang)) { g_object_unref (lang); return NULL; } return lang; } const char *moo_edit_lang_get_id (MooEditLang *lang) { g_return_val_if_fail (MOO_IS_EDIT_LANG (lang), NULL); return lang->priv->id; } const char *moo_edit_lang_get_name (MooEditLang *lang) { g_return_val_if_fail (MOO_IS_EDIT_LANG (lang), NULL); return lang->priv->name; } const char *moo_edit_lang_get_section (MooEditLang *lang) { g_return_val_if_fail (MOO_IS_EDIT_LANG (lang), NULL); return lang->priv->section; } /* The list must be freed and the tags unref'ed */ GSList *moo_edit_lang_get_tags (MooEditLang *lang) { GSList *list = NULL, *l = NULL; g_return_val_if_fail (MOO_IS_EDIT_LANG (lang), NULL); if (!lang->priv->loaded) moo_edit_lang_load_full (lang); for (l = lang->priv->tags; l != NULL; l = l->next) { GtkTextTag *copy; char *style_id; char *tag_id; copy = gtk_source_tag_copy (GTK_SOURCE_TAG (l->data)); list = g_slist_prepend (list, copy); tag_id = gtk_source_tag_get_id (GTK_SOURCE_TAG (l->data)); style_id = g_hash_table_lookup (lang->priv->tag_id_to_style_id, tag_id); if (style_id) { GPtrArray *tags; GtkSourceTagStyle *style; tags = g_hash_table_lookup (lang->priv->style_id_to_tags, style_id); if (!tags) { tags = g_ptr_array_new (); g_hash_table_insert (lang->priv->style_id_to_tags, g_strdup (style_id), tags); } g_ptr_array_add (tags, copy); g_object_weak_ref (G_OBJECT (copy), (GWeakNotify) remove_tag, tags); style = g_hash_table_lookup (lang->priv->style_id_to_style, style_id); if (!style) g_critical ("no style with id '%s'", style_id); else gtk_source_tag_set_style (GTK_SOURCE_TAG (copy), style); } g_free (tag_id); } list = g_slist_reverse (list); return list; } GtkSourceTag *moo_edit_lang_get_tag (MooEditLang *lang, const char *tag_id) { GSList *l; GtkSourceTag *tag; g_return_val_if_fail (MOO_IS_EDIT_LANG (lang) && tag_id != NULL, NULL); if (!lang->priv->loaded) moo_edit_lang_load_full (lang); for (l = lang->priv->tags; l != NULL; l = l->next) { char *id; tag = GTK_SOURCE_TAG (l->data); id = gtk_source_tag_get_id (tag); if (!strcmp (id, tag_id)) { g_free (id); return tag; } g_free (id); } return NULL; } gunichar moo_edit_lang_get_escape_char (MooEditLang *lang) { g_return_val_if_fail (MOO_IS_EDIT_LANG (lang), 0); if (!lang->priv->loaded) moo_edit_lang_load_full (lang); return lang->priv->escape_char; } void moo_edit_lang_set_escape_char (MooEditLang *lang, gunichar escape_char) { g_return_if_fail (MOO_IS_EDIT_LANG (lang)); if (!lang->priv->loaded) moo_edit_lang_load_full (lang); lang->priv->escape_char = escape_char; } /* Should free the list (and free each string in it also). */ GSList *moo_edit_lang_get_mime_types (MooEditLang *lang) { g_return_val_if_fail (MOO_IS_EDIT_LANG (lang), NULL); if (!lang->priv->loaded) moo_edit_lang_load_full (lang); return copy_string_slist (lang->priv->mime_types); } void moo_edit_lang_set_mime_types (MooEditLang *lang, const GSList *mime_types) { g_return_if_fail (MOO_IS_EDIT_LANG (lang)); if (!lang->priv->loaded) moo_edit_lang_load_full (lang); free_string_slist (lang->priv->mime_types); lang->priv->mime_types = copy_string_slist (mime_types); } GSList *moo_edit_lang_get_extensions (MooEditLang *lang) { g_return_val_if_fail (MOO_IS_EDIT_LANG (lang), NULL); if (!lang->priv->loaded) moo_edit_lang_load_full (lang); return copy_string_slist (lang->priv->extensions); } void moo_edit_lang_set_extensions (MooEditLang *lang, const GSList *extensions) { g_return_if_fail (MOO_IS_EDIT_LANG (lang)); if (!lang->priv->loaded) moo_edit_lang_load_full (lang); free_string_slist (lang->priv->extensions); lang->priv->extensions = copy_string_slist (extensions); } /* Allocates new null-terminated array; even indexes are style ids, odd are style names */ GPtrArray *moo_edit_lang_get_style_names (MooEditLang *lang) { GPtrArray *result; g_return_val_if_fail (MOO_IS_EDIT_LANG (lang), NULL); if (!lang->priv->loaded) moo_edit_lang_load_full (lang); result = g_ptr_array_new (); g_hash_table_foreach (lang->priv->style_id_to_style_name, (GHFunc) add_string_pair, result); g_ptr_array_add (result, NULL); return result; } const GtkSourceTagStyle *moo_edit_lang_get_style (MooEditLang *lang, const char *style_id) { g_return_val_if_fail (MOO_IS_EDIT_LANG (lang) && style_id != NULL, NULL); if (!lang->priv->loaded) moo_edit_lang_load_full (lang); return g_hash_table_lookup (lang->priv->style_id_to_style, style_id); } void moo_edit_lang_set_style (MooEditLang *lang, const char *style_id, const GtkSourceTagStyle *style) { GPtrArray *tags; GSList *m; g_return_if_fail (MOO_IS_EDIT_LANG (lang)); g_return_if_fail (style_id != NULL && style != NULL); if (!lang->priv->loaded) moo_edit_lang_load_full (lang); g_return_if_fail (g_hash_table_lookup (lang->priv->style_id_to_style, style_id) != NULL); moo_edit_lang_write_style_setting (lang, style_id, style); g_hash_table_insert (lang->priv->style_id_to_style, g_strdup (style_id), gtk_source_tag_style_copy (style)); tags = g_hash_table_lookup (lang->priv->style_id_to_tags, style_id); if (tags) { guint i; for (i = 0; i < tags->len; ++i) gtk_source_tag_set_style (GTK_SOURCE_TAG (g_ptr_array_index(tags, i)), style); } /* TODO TODO something to all this stuff */ /* 'master copies' of tags are not stored in lang->priv->style_id_to_tags because of weak_ref issues */ m = g_slist_find_custom (lang->priv->tags, style_id, (GCompareFunc) strcmp); g_return_if_fail (m != NULL); gtk_source_tag_set_style (GTK_SOURCE_TAG (m->data), style); } #ifndef MOO_USE_XML gboolean moo_edit_lang_load_description (G_GNUC_UNUSED MooEditLang *lang) { g_warning ("XML support disabled, can't load language description"); return FALSE; } gboolean moo_edit_lang_load_full (MooEditLang *lang) { g_return_val_if_fail (MOO_IS_EDIT_LANG (lang), FALSE); g_return_val_if_fail (!lang->priv->loaded, FALSE); lang->priv->loaded = TRUE; g_warning ("XML support disabled, can't load language"); return FALSE; } #endif /* MOO_USE_XML */ static void free_tag_array (GPtrArray *array) { g_ptr_array_free (array, TRUE); } static void remove_tag (GPtrArray *array, GObject *tag) { g_return_if_fail (g_ptr_array_remove_fast (array, tag)); } static void remove_weak_ref (G_GNUC_UNUSED const char *style_id, GPtrArray *tags_array) { guint i; for (i = 0; i < tags_array->len; ++i) g_object_weak_unref (G_OBJECT (g_ptr_array_index (tags_array, i)), (GWeakNotify) remove_tag, tags_array); } static GSList *copy_string_slist (const GSList *list) { GSList *copy = NULL; const GSList *l; for (l = list; l != NULL; l = l->next) copy = g_slist_prepend (copy, g_strdup ((char*)l->data)); copy = g_slist_reverse (copy); return copy; } static void free_string_slist (GSList *list) { GSList *l; for (l = list; l != NULL; l = l->next) g_free (l->data); g_slist_free (list); } static void add_string_pair (const char *id, const char *name, GPtrArray *array) { g_ptr_array_add (array, g_strdup (id)); g_ptr_array_add (array, g_strdup (name)); } /****************************************************************************/ /* Default styles */ /***/ typedef struct { char *id; char *name; GtkSourceTagStyle *style; } Style; /* char* -> Style* */ static GHashTable *default_styles = NULL; static void create_default_styles (void); static Style *new_style (const char *id, const char *name, GtkSourceTagStyle *style); static GtkSourceTagStyle *new_tag_style (const char *fg, const char *bg, gboolean italic, gboolean bold, gboolean underline, gboolean strikethrough); static void free_style (Style *s); static void add_id_name_pair (const char *id, const Style *style, GPtrArray *array); static Style *new_style (const char *id, const char *name, GtkSourceTagStyle *style) { Style *s = g_new (Style, 1); s->id = g_strdup (id); s->name = g_strdup (name); s->style = style; return s; } static GtkSourceTagStyle *new_tag_style (const char *fg, const char *bg, gboolean italic, gboolean bold, gboolean underline, gboolean strikethrough) { GtkSourceTagStyle *s = gtk_source_tag_style_new (); GdkColor color; s->is_default = TRUE; s->italic = italic; s->bold = bold; s->underline = underline; s->strikethrough = strikethrough; s->mask = 0; if (fg) { if (!gdk_color_parse (fg, &color)) g_warning ("could not parse color %s", fg); else { s->foreground = color; s->mask |= GTK_SOURCE_TAG_STYLE_USE_FOREGROUND; } } if (bg) { if (!gdk_color_parse (bg, &color)) g_warning ("could not parse color %s", bg); else { s->background = color; s->mask |= GTK_SOURCE_TAG_STYLE_USE_BACKGROUND; } } return s; } static void free_style (Style *s) { g_free (s->id); g_free (s->name); gtk_source_tag_style_free (s->style); g_free (s); } static void add_id_name_pair (const char *id, const Style *style, GPtrArray *array) { g_ptr_array_add (array, g_strdup (id)); g_ptr_array_add (array, g_strdup (style->name)); } static void create_default_styles (void) { if (default_styles) return; default_styles = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)free_style); g_hash_table_insert (default_styles, g_strdup (MOO_EDIT_STYLE_BASE_N), new_style (MOO_EDIT_STYLE_BASE_N, "Base-N Integer", new_tag_style ("#008080", NULL, FALSE, FALSE, FALSE, FALSE))); g_hash_table_insert (default_styles, g_strdup (MOO_EDIT_STYLE_KEYWORD), new_style (MOO_EDIT_STYLE_KEYWORD, "Keyword", new_tag_style (NULL, NULL, FALSE, TRUE, FALSE, FALSE))); g_hash_table_insert (default_styles, g_strdup (MOO_EDIT_STYLE_DATA_TYPE), new_style (MOO_EDIT_STYLE_DATA_TYPE, "Data Type", new_tag_style ("#800000", NULL, FALSE, FALSE, FALSE, FALSE))); g_hash_table_insert (default_styles, g_strdup (MOO_EDIT_STYLE_DECIMAL), new_style (MOO_EDIT_STYLE_DECIMAL, "Decimal", new_tag_style ("#0000FF", NULL, FALSE, FALSE, FALSE, FALSE))); g_hash_table_insert (default_styles, g_strdup (MOO_EDIT_STYLE_FLOAT), new_style (MOO_EDIT_STYLE_FLOAT, "Float", new_tag_style ("#800080", NULL, FALSE, FALSE, FALSE, FALSE))); g_hash_table_insert (default_styles, g_strdup (MOO_EDIT_STYLE_CHAR), new_style (MOO_EDIT_STYLE_CHAR, "Char", new_tag_style ("#FF00FF", NULL, FALSE, FALSE, FALSE, FALSE))); g_hash_table_insert (default_styles, g_strdup (MOO_EDIT_STYLE_STRING), new_style (MOO_EDIT_STYLE_STRING, "String", new_tag_style ("#FF0000", NULL, FALSE, FALSE, FALSE, FALSE))); g_hash_table_insert (default_styles, g_strdup (MOO_EDIT_STYLE_COMMENT), new_style (MOO_EDIT_STYLE_COMMENT, "Comment", new_tag_style ("#808080", NULL, TRUE, FALSE, FALSE, FALSE))); g_hash_table_insert (default_styles, g_strdup (MOO_EDIT_STYLE_OTHERS), new_style (MOO_EDIT_STYLE_OTHERS, "Others", new_tag_style ("#008000", NULL, FALSE, FALSE, FALSE, FALSE))); g_hash_table_insert (default_styles, g_strdup (MOO_EDIT_STYLE_ALERT), new_style (MOO_EDIT_STYLE_ALERT, "Alert", new_tag_style ("#FFFFFF", "#FF0000", FALSE, TRUE, FALSE, FALSE))); g_hash_table_insert (default_styles, g_strdup (MOO_EDIT_STYLE_FUNCTION), new_style (MOO_EDIT_STYLE_FUNCTION, "Function", new_tag_style ("#000080", NULL, FALSE, FALSE, FALSE, FALSE))); g_hash_table_insert (default_styles, g_strdup (MOO_EDIT_STYLE_ERROR), new_style (MOO_EDIT_STYLE_ERROR, "Error", new_tag_style ("#FF0000", NULL, FALSE, FALSE, TRUE, FALSE))); } /* Allocates new null-terminated array; even indexes are style ids, odd are style names */ GPtrArray *moo_edit_lang_get_default_style_names (void) { GPtrArray *array; create_default_styles (); array = g_ptr_array_new (); g_hash_table_foreach (default_styles, (GHFunc) add_id_name_pair, array); g_ptr_array_add (array, NULL); return array; } GtkSourceTagStyle *moo_edit_lang_get_default_style (const char *style_id) { Style *s; g_return_val_if_fail (style_id != NULL, NULL); create_default_styles (); s = g_hash_table_lookup (default_styles, style_id); if (s) return gtk_source_tag_style_copy (s->style); else return NULL; } void moo_edit_style_load (const char *lang_id, const char *style_id, GtkSourceTagStyle *style) { const char *s; const GdkColor *color; g_return_if_fail (lang_id != NULL && style_id != NULL && style != NULL); create_default_styles (); s = moo_edit_style_setting (lang_id, style_id, MOO_EDIT_PREFS_FOREGROUND); color = moo_prefs_get_color (s); if (color) { style->foreground = *color; style->mask |= GTK_SOURCE_TAG_STYLE_USE_FOREGROUND; } s = moo_edit_style_setting (lang_id, style_id, MOO_EDIT_PREFS_BACKGROUND); color = moo_prefs_get_color (s); if (color) { style->background = *color; style->mask |= GTK_SOURCE_TAG_STYLE_USE_BACKGROUND; } s = moo_edit_style_setting (lang_id, style_id, MOO_EDIT_PREFS_BOLD); if (moo_prefs_get (s)) style->bold = moo_prefs_get_bool (s); s = moo_edit_style_setting (lang_id, style_id, MOO_EDIT_PREFS_ITALIC); if (moo_prefs_get (s)) style->italic = moo_prefs_get_bool (s); s = moo_edit_style_setting (lang_id, style_id, MOO_EDIT_PREFS_UNDERLINE); if (moo_prefs_get (s)) style->underline = moo_prefs_get_bool (s); s = moo_edit_style_setting (lang_id, style_id, MOO_EDIT_PREFS_STRIKETHROUGH); if (moo_prefs_get (s)) style->strikethrough = moo_prefs_get_bool (s); } const char *moo_edit_style_setting (const char *lang_id, const char *style_id, const char *setting) { static char *s = NULL; g_return_val_if_fail (lang_id != NULL && style_id != NULL && setting != NULL, NULL); g_free (s); return s = g_strdup_printf (MOO_EDIT_PREFS_PREFIX "/syntax/%s/%s/%s", lang_id, style_id, setting); } #define color_differ(c1,c2) \ ((c1)->red != (c2)->red || \ (c1)->green != (c2)->green || \ (c1)->blue != (c2)->blue) static void moo_edit_lang_write_style_setting (MooEditLang *lang, const char *style_id, const GtkSourceTagStyle *style) { const char *lang_id; const char *setting; const GdkColor *color; const GtkSourceTagStyle *old_style; old_style = g_hash_table_lookup (lang->priv->style_id_to_style, style_id); g_return_if_fail (old_style != NULL); lang_id = lang->priv->id; if ((old_style->mask & GTK_SOURCE_TAG_STYLE_USE_BACKGROUND) != (style->mask & GTK_SOURCE_TAG_STYLE_USE_BACKGROUND) || ((old_style->mask & GTK_SOURCE_TAG_STYLE_USE_BACKGROUND) && color_differ (&(old_style->background), &(style->background)))) { setting = moo_edit_style_setting (lang_id, style_id, MOO_EDIT_PREFS_BACKGROUND); if (style->mask & GTK_SOURCE_TAG_STYLE_USE_BACKGROUND) { color = moo_prefs_get_color (setting); if (!color || color_differ (color, &(style->background))) moo_prefs_set_color (setting, &(style->background)); } else if (moo_prefs_get (setting)) moo_prefs_set (setting, NULL); } if ((old_style->mask & GTK_SOURCE_TAG_STYLE_USE_FOREGROUND) != (style->mask & GTK_SOURCE_TAG_STYLE_USE_FOREGROUND) || ((old_style->mask & GTK_SOURCE_TAG_STYLE_USE_FOREGROUND) && color_differ (&(old_style->foreground), &(style->foreground)))) { setting = moo_edit_style_setting (lang_id, style_id, MOO_EDIT_PREFS_FOREGROUND); if (style->mask & GTK_SOURCE_TAG_STYLE_USE_FOREGROUND) { color = moo_prefs_get_color (setting); if (!color || color_differ (color, &(style->foreground))) moo_prefs_set_color (setting, &(style->foreground)); } else if (moo_prefs_get (setting)) moo_prefs_set (setting, NULL); } if (old_style->bold != style->bold) { setting = moo_edit_style_setting (lang_id, style_id, MOO_EDIT_PREFS_BOLD); if (moo_prefs_get_bool (setting) != style->bold) moo_prefs_set_bool (setting, style->bold); } if (old_style->italic != style->italic) { setting = moo_edit_style_setting (lang_id, style_id, MOO_EDIT_PREFS_ITALIC); if (moo_prefs_get_bool (setting) != style->italic) moo_prefs_set_bool (setting, style->italic); } if (old_style->underline != style->underline) { setting = moo_edit_style_setting (lang_id, style_id, MOO_EDIT_PREFS_UNDERLINE); if (moo_prefs_get_bool (setting) != style->underline) moo_prefs_set_bool (setting, style->underline); } if (old_style->strikethrough != style->strikethrough) { setting = moo_edit_style_setting (lang_id, style_id, MOO_EDIT_PREFS_STRIKETHROUGH); if (moo_prefs_get_bool (setting) != style->strikethrough) moo_prefs_set_bool (setting, style->strikethrough); } } const char *moo_edit_lang_get_brackets (MooEditLang *lang) { g_return_val_if_fail (MOO_IS_EDIT_LANG (lang), NULL); return lang->priv->brackets; }