medit/moo/mooedit/gtksourceview/upstream/gtksourcelanguage.c

918 lines
22 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*-
* gtksourcelanguage.c
*
* Copyright (C) 2003 - Paolo Maggi <paolo.maggi@polito.it>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef G_OS_WIN32
#include <io.h>
#endif
#include <string.h>
#include <fcntl.h>
#include <libxml/xmlreader.h>
#include <glib/gstdio.h>
#include "gtksourceview-i18n.h"
#include "gtksourcelanguage-private.h"
#include "gtksourcelanguage.h"
#include "gtksourceview-marshal.h"
#define DEFAULT_SECTION _("Others")
/* Properties */
enum {
PROP_0,
PROP_ID,
PROP_NAME,
PROP_SECTION,
PROP_HIDDEN
};
G_DEFINE_TYPE (GtkSourceLanguage, gtk_source_language, G_TYPE_OBJECT)
static GtkSourceLanguage *process_language_node (xmlTextReaderPtr reader,
const gchar *filename);
static gboolean force_styles (GtkSourceLanguage *language);
GtkSourceLanguage *
_gtk_source_language_new_from_file (const gchar *filename,
GtkSourceLanguageManager *lm)
{
GtkSourceLanguage *lang = NULL;
xmlTextReaderPtr reader = NULL;
gint ret;
gint fd;
g_return_val_if_fail (filename != NULL, NULL);
g_return_val_if_fail (lm != NULL, NULL);
/*
* Use fd instead of filename so that it's utf8 safe on w32.
*/
fd = g_open (filename, O_RDONLY, 0);
if (fd != -1)
reader = xmlReaderForFd (fd, filename, NULL, 0);
if (reader != NULL)
{
ret = xmlTextReaderRead (reader);
while (ret == 1)
{
if (xmlTextReaderNodeType (reader) == 1)
{
xmlChar *name;
name = xmlTextReaderName (reader);
if (xmlStrcmp (name, BAD_CAST "language") == 0)
{
lang = process_language_node (reader, filename);
ret = 0;
}
xmlFree (name);
}
if (ret == 1)
ret = xmlTextReaderRead (reader);
}
xmlFreeTextReader (reader);
close (fd);
if (ret != 0)
{
g_warning("Failed to parse '%s'", filename);
return NULL;
}
}
else
{
g_warning("Unable to open '%s'", filename);
}
if (lang != NULL)
{
lang->priv->language_manager = lm;
g_object_add_weak_pointer (G_OBJECT (lm),
(gpointer) &lang->priv->language_manager);
}
return lang;
}
static void
gtk_source_language_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkSourceLanguage *language;
g_return_if_fail (GTK_IS_SOURCE_LANGUAGE (object));
language = GTK_SOURCE_LANGUAGE (object);
switch (prop_id)
{
case PROP_ID:
g_value_set_string (value, language->priv->id);
break;
case PROP_NAME:
g_value_set_string (value, language->priv->name);
break;
case PROP_SECTION:
g_value_set_string (value, language->priv->name);
break;
case PROP_HIDDEN:
g_value_set_boolean (value, language->priv->hidden);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_source_language_dispose (GObject *object)
{
GtkSourceLanguage *lang;
lang = GTK_SOURCE_LANGUAGE (object);
if (lang->priv->language_manager != NULL)
{
g_object_remove_weak_pointer (G_OBJECT (lang->priv->language_manager),
(gpointer) &lang->priv->language_manager);
lang->priv->language_manager = NULL;
}
G_OBJECT_CLASS (gtk_source_language_parent_class)->dispose (object);
}
static void
gtk_source_language_finalize (GObject *object)
{
GtkSourceLanguage *lang;
lang = GTK_SOURCE_LANGUAGE (object);
if (lang->priv->ctx_data != NULL)
g_critical ("context data not freed in gtk_source_language_finalize");
g_free (lang->priv->lang_file_name);
g_free (lang->priv->translation_domain);
g_free (lang->priv->name);
g_free (lang->priv->section);
g_free (lang->priv->id);
g_hash_table_destroy (lang->priv->properties);
g_hash_table_destroy (lang->priv->styles);
G_OBJECT_CLASS (gtk_source_language_parent_class)->finalize (object);
}
static void
gtk_source_language_class_init (GtkSourceLanguageClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->get_property = gtk_source_language_get_property;
object_class->dispose = gtk_source_language_dispose;
object_class->finalize = gtk_source_language_finalize;
g_object_class_install_property (object_class,
PROP_ID,
g_param_spec_string ("id",
_("Language id"),
_("Language id"),
NULL,
G_PARAM_READABLE));
g_object_class_install_property (object_class,
PROP_ID,
g_param_spec_string ("name",
_("Language name"),
_("Language name"),
NULL,
G_PARAM_READABLE));
g_object_class_install_property (object_class,
PROP_ID,
g_param_spec_string ("section",
_("Language section"),
_("Language section"),
NULL,
G_PARAM_READABLE));
g_object_class_install_property (object_class,
PROP_HIDDEN,
g_param_spec_boolean ("hidden",
_("Hidden"),
_("Whether the language should be hidden from the user"),
FALSE,
G_PARAM_READABLE));
g_type_class_add_private (object_class, sizeof(GtkSourceLanguagePrivate));
}
static void
gtk_source_language_init (GtkSourceLanguage *lang)
{
lang->priv = G_TYPE_INSTANCE_GET_PRIVATE (lang, GTK_TYPE_SOURCE_LANGUAGE,
GtkSourceLanguagePrivate);
lang->priv->styles = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
(GDestroyNotify)_gtk_source_style_info_free);
lang->priv->properties = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
}
static gboolean
string_to_bool (const gchar *string)
{
if (!g_ascii_strcasecmp (string, "yes") ||
!g_ascii_strcasecmp (string, "true") ||
!g_ascii_strcasecmp (string, "1"))
return TRUE;
else if (!g_ascii_strcasecmp (string, "no") ||
!g_ascii_strcasecmp (string, "false") ||
!g_ascii_strcasecmp (string, "0"))
return FALSE;
else
g_return_val_if_reached (FALSE);
}
static void
process_properties (xmlTextReaderPtr reader,
GtkSourceLanguage *language)
{
xmlNodePtr child;
xmlNodePtr node = NULL;
while (node == NULL && xmlTextReaderRead (reader) == 1)
{
xmlChar *name;
if (xmlTextReaderNodeType (reader) != 1)
continue;
name = xmlTextReaderName (reader);
if (xmlStrcmp (name, BAD_CAST "metadata") != 0)
{
xmlFree (name);
continue;
}
xmlFree (name);
node = xmlTextReaderExpand (reader);
if (node == NULL)
return;
}
if (node == NULL)
return;
for (child = node->children; child != NULL; child = child->next)
{
xmlChar *name;
xmlChar *content;
if (child->type != XML_ELEMENT_NODE ||
xmlStrcmp (child->name, BAD_CAST "property") != 0)
continue;
name = xmlGetProp (child, BAD_CAST "name");
content = xmlNodeGetContent (child);
if (name != NULL && content != NULL)
g_hash_table_insert (language->priv->properties,
g_strdup ((gchar *) name),
g_strdup ((gchar *) content));
xmlFree (name);
xmlFree (content);
}
}
static GtkSourceLanguage *
process_language_node (xmlTextReaderPtr reader, const gchar *filename)
{
xmlChar *version;
xmlChar *tmp;
xmlChar *untranslated_name;
GtkSourceLanguage *lang;
lang = g_object_new (GTK_TYPE_SOURCE_LANGUAGE, NULL);
lang->priv->lang_file_name = g_strdup (filename);
tmp = xmlTextReaderGetAttribute (reader, BAD_CAST "translation-domain");
lang->priv->translation_domain = g_strdup ((gchar*) tmp);
xmlFree (tmp);
tmp = xmlTextReaderGetAttribute (reader, BAD_CAST "hidden");
if (tmp != NULL)
lang->priv->hidden = string_to_bool ((gchar*) tmp);
else
lang->priv->hidden = FALSE;
xmlFree (tmp);
tmp = xmlTextReaderGetAttribute (reader, BAD_CAST "mimetypes");
if (tmp != NULL)
g_hash_table_insert (lang->priv->properties,
g_strdup ("mimetypes"),
g_strdup ((char*) tmp));
xmlFree (tmp);
tmp = xmlTextReaderGetAttribute (reader, BAD_CAST "globs");
if (tmp != NULL)
g_hash_table_insert (lang->priv->properties,
g_strdup ("globs"),
g_strdup ((char*) tmp));
xmlFree (tmp);
tmp = xmlTextReaderGetAttribute (reader, BAD_CAST "_name");
if (tmp == NULL)
{
tmp = xmlTextReaderGetAttribute (reader, BAD_CAST "name");
if (tmp == NULL)
{
g_warning ("Impossible to get language name from file '%s'",
filename);
g_object_unref (lang);
return NULL;
}
lang->priv->name = g_strdup ((char*) tmp);
untranslated_name = tmp;
}
else
{
lang->priv->name = _gtk_source_language_translate_string (lang, (gchar*) tmp);
untranslated_name = tmp;
}
tmp = xmlTextReaderGetAttribute (reader, BAD_CAST "id");
if (tmp != NULL)
{
lang->priv->id = g_ascii_strdown ((gchar*) tmp, -1);
}
else
{
lang->priv->id = g_ascii_strdown ((gchar*) untranslated_name, -1);
}
xmlFree (tmp);
xmlFree (untranslated_name);
tmp = xmlTextReaderGetAttribute (reader, BAD_CAST "_section");
if (tmp == NULL)
{
tmp = xmlTextReaderGetAttribute (reader, BAD_CAST "section");
if (tmp == NULL)
lang->priv->section = g_strdup (DEFAULT_SECTION);
else
lang->priv->section = g_strdup ((gchar *) tmp);
xmlFree (tmp);
}
else
{
lang->priv->section = _gtk_source_language_translate_string (lang, (gchar*) tmp);
xmlFree (tmp);
}
version = xmlTextReaderGetAttribute (reader, BAD_CAST "version");
if (version == NULL)
{
g_warning ("Impossible to get version number from file '%s'",
filename);
g_object_unref (lang);
return NULL;
}
if (xmlStrcmp (version , BAD_CAST "1.0") == 0)
{
lang->priv->version = GTK_SOURCE_LANGUAGE_VERSION_1_0;
}
else if (xmlStrcmp (version, BAD_CAST "2.0") == 0)
{
lang->priv->version = GTK_SOURCE_LANGUAGE_VERSION_2_0;
}
else
{
g_warning ("Unsupported language spec version '%s' in file '%s'",
(gchar*) version, filename);
xmlFree (version);
g_object_unref (lang);
return NULL;
}
xmlFree (version);
if (lang->priv->version == GTK_SOURCE_LANGUAGE_VERSION_2_0)
process_properties (reader, lang);
return lang;
}
gchar *
_gtk_source_language_translate_string (GtkSourceLanguage *language,
const gchar *string)
{
g_return_val_if_fail (string != NULL, NULL);
return GD_(language->priv->translation_domain, string);
}
/**
* gtk_source_language_get_id:
* @language: a #GtkSourceLanguage.
*
* Returns the ID of the language. The ID is not locale-dependent.
*
* Returns: the ID of @language.
* The returned string is owned by @language and should not be freed
* or modified.
**/
const gchar *
gtk_source_language_get_id (GtkSourceLanguage *language)
{
g_return_val_if_fail (GTK_IS_SOURCE_LANGUAGE (language), NULL);
g_return_val_if_fail (language->priv->id != NULL, NULL);
return language->priv->id;
}
/**
* gtk_source_language_get_name:
* @language: a #GtkSourceLanguage.
*
* Returns the localized name of the language.
*
* Returns: the name of @language.
* The returned string is owned by @language and should not be freed
* or modified.
**/
const gchar *
gtk_source_language_get_name (GtkSourceLanguage *language)
{
g_return_val_if_fail (GTK_IS_SOURCE_LANGUAGE (language), NULL);
g_return_val_if_fail (language->priv->name != NULL, NULL);
return language->priv->name;
}
/**
* gtk_source_language_get_section:
* @language: a #GtkSourceLanguage.
*
* Returns the localized section of the language.
* Each language belong to a section (ex. HTML belogs to the
* Markup section).
*
* Returns: the section of @language.
* The returned string is owned by @language and should not be freed
* or modified.
**/
const gchar *
gtk_source_language_get_section (GtkSourceLanguage *language)
{
g_return_val_if_fail (GTK_IS_SOURCE_LANGUAGE (language), NULL);
g_return_val_if_fail (language->priv->section != NULL, NULL);
return language->priv->section;
}
/**
* gtk_source_language_get_hidden:
* @language: a #GtkSourceLanguage
*
* Returns whether the language should be hidden from the user.
*
* Returns: TRUE if the language should be hidden, FALSE otherwise.
*/
gboolean
gtk_source_language_get_hidden (GtkSourceLanguage *language)
{
g_return_val_if_fail (GTK_IS_SOURCE_LANGUAGE (language), FALSE);
return language->priv->hidden;
}
/**
* gtk_source_language_get_metadata:
* @language: a #GtkSourceLanguage.
* @name: metadata property name.
*
* Returns: value of property @name stored in the metadata of @language
* or %NULL if language doesn't contain that metadata property.
* The returned string is owned by @language and should not be freed
* or modified.
**/
const gchar *
gtk_source_language_get_metadata (GtkSourceLanguage *language,
const gchar *name)
{
g_return_val_if_fail (GTK_IS_SOURCE_LANGUAGE (language), NULL);
g_return_val_if_fail (name != NULL, NULL);
return g_hash_table_lookup (language->priv->properties, name);
}
/**
* gtk_source_language_get_mime_types:
*
* @language: a #GtkSourceLanguage.
*
* Returns the mime types associated to this language. This is just
* an utility wrapper around gtk_source_language_get_metadata() to
* retrieve the "mimetypes" metadata property and split it into an
* array.
*
* Returns: a newly-allocated %NULL terminated array containing
* the mime types or %NULL if no mime types are found.
* The returned array must be freed with g_strfreev().
**/
gchar **
gtk_source_language_get_mime_types (GtkSourceLanguage *language)
{
const gchar *mimetypes;
g_return_val_if_fail (GTK_IS_SOURCE_LANGUAGE (language), NULL);
mimetypes = gtk_source_language_get_metadata (language, "mimetypes");
if (mimetypes == NULL)
return NULL;
return g_strsplit (mimetypes, ";", 0);
}
/**
* gtk_source_language_get_globs:
*
* @language: a #GtkSourceLanguage.
*
* Returns the globs associated to this language. This is just
* an utility wrapper around gtk_source_language_get_metadata() to
* retrieve the "globs" metadata property and split it into an array.
*
* Returns: a newly-allocated %NULL terminated array containing
* the globs or %NULL if no globs are found.
* The returned array must be freed with g_strfreev().
**/
gchar **
gtk_source_language_get_globs (GtkSourceLanguage *language)
{
const gchar *globs;
g_return_val_if_fail (GTK_IS_SOURCE_LANGUAGE (language), NULL);
globs = gtk_source_language_get_metadata (language, "globs");
if (globs == NULL)
return NULL;
return g_strsplit (globs, ";", 0);
}
/**
* _gtk_source_language_get_language_manager:
* @language: a #GtkSourceLanguage.
*
* Returns: #GtkSourceLanguageManager for @language.
**/
GtkSourceLanguageManager *
_gtk_source_language_get_language_manager (GtkSourceLanguage *language)
{
g_return_val_if_fail (GTK_IS_SOURCE_LANGUAGE (language), NULL);
g_return_val_if_fail (language->priv->id != NULL, NULL);
return language->priv->language_manager;
}
/* Highlighting engine creation ------------------------------------------ */
static void
copy_style_info (const char *style_id,
GtkSourceStyleInfo *info,
GHashTable *dest)
{
g_hash_table_insert (dest, g_strdup (style_id),
_gtk_source_style_info_copy (info));
}
void
_gtk_source_language_define_language_styles (GtkSourceLanguage *lang)
{
static const gchar *alias[][2] = {
{"Base-N Integer", "def:base-n-integer"},
{"Character", "def:character"},
{"Comment", "def:comment"},
{"Function", "def:function"},
{"Decimal", "def:decimal"},
{"Floating Point", "def:floating-point"},
{"Keyword", "def:keyword"},
{"Preprocessor", "def:preprocessor"},
{"String", "def:string"},
{"Specials", "def:specials"},
{"Data Type", "def:type"},
{NULL, NULL}};
gint i = 0;
GtkSourceLanguageManager *lm;
GtkSourceLanguage *def_lang;
while (alias[i][0] != NULL)
{
GtkSourceStyleInfo *info;
info = _gtk_source_style_info_new (alias[i][0], alias[i][1]);
g_hash_table_insert (lang->priv->styles,
g_strdup (alias[i][0]),
info);
++i;
}
/* We translate String to def:string, but def:string is mapped-to
* def:constant in def.lang, so we got to take style mappings from def.lang */
lm = _gtk_source_language_get_language_manager (lang);
def_lang = gtk_source_language_manager_get_language (lm, "def");
if (def_lang != NULL)
{
force_styles (def_lang);
g_hash_table_foreach (def_lang->priv->styles,
(GHFunc) copy_style_info,
lang->priv->styles);
}
}
/* returns new reference, which _must_ be unref'ed */
static GtkSourceContextData *
gtk_source_language_parse_file (GtkSourceLanguage *language)
{
if (language->priv->ctx_data == NULL)
{
gboolean success = FALSE;
GtkSourceContextData *ctx_data;
if (language->priv->language_manager == NULL)
{
g_critical ("_gtk_source_language_create_engine() is called after "
"language manager was finalized");
}
else
{
ctx_data = _gtk_source_context_data_new (language);
switch (language->priv->version)
{
case GTK_SOURCE_LANGUAGE_VERSION_1_0:
success = _gtk_source_language_file_parse_version1 (language, ctx_data);
break;
case GTK_SOURCE_LANGUAGE_VERSION_2_0:
success = _gtk_source_language_file_parse_version2 (language, ctx_data);
break;
}
if (!success)
_gtk_source_context_data_unref (ctx_data);
else
language->priv->ctx_data = ctx_data;
}
}
else
{
_gtk_source_context_data_ref (language->priv->ctx_data);
}
return language->priv->ctx_data;
}
GtkSourceEngine *
_gtk_source_language_create_engine (GtkSourceLanguage *language)
{
GtkSourceContextEngine *ce = NULL;
GtkSourceContextData *ctx_data;
ctx_data = gtk_source_language_parse_file (language);
if (ctx_data != NULL)
{
ce = _gtk_source_context_engine_new (ctx_data);
_gtk_source_context_data_unref (ctx_data);
}
return ce ? GTK_SOURCE_ENGINE (ce) : NULL;
}
typedef struct _AddStyleIdData AddStyleIdData;
struct _AddStyleIdData
{
gchar *language_id;
GPtrArray *ids_array;
};
static void
add_style_id (gchar *id, G_GNUC_UNUSED gpointer value, AddStyleIdData *data)
{
if (g_str_has_prefix (id, data->language_id))
g_ptr_array_add (data->ids_array, g_strdup (id));
}
static gchar **
get_style_ids (GtkSourceLanguage *language)
{
GPtrArray *ids_array;
AddStyleIdData data;
g_return_val_if_fail (language->priv->styles != NULL, NULL);
ids_array = g_ptr_array_new ();
data.language_id = g_strdup_printf ("%s:", language->priv->id);
data.ids_array = ids_array;
g_hash_table_foreach (language->priv->styles,
(GHFunc) add_style_id,
&data);
g_free (data.language_id);
if (ids_array->len == 0)
{
/* No style defined in this language */
g_ptr_array_free (ids_array, TRUE);
return NULL;
}
else
{
/* Terminate the array with NULL */
g_ptr_array_add (ids_array, NULL);
return (gchar **)g_ptr_array_free (ids_array, FALSE);
}
}
static gboolean
force_styles (GtkSourceLanguage *language)
{
/* To be sure to have the list of styles we need to parse lang file
* as if we were to create an engine. In the future we can improve
* this by parsing styles only.
*/
if (!language->priv->styles_loaded && language->priv->ctx_data == NULL)
{
GtkSourceContextData *ctx_data;
ctx_data = gtk_source_language_parse_file (language);
if (ctx_data == NULL)
return FALSE;
language->priv->styles_loaded = TRUE;
_gtk_source_context_data_unref (ctx_data);
}
return TRUE;
}
/**
* gtk_source_language_get_style_ids:
*
* @language: a #GtkSourceLanguage
*
* Returns the ids of the styles defined by this @language.
*
* Returns: a %NULL terminated array containing
* ids of the styles defined by this @language or %NULL if no style is
* defined. The returned array must be freed with g_strfreev().
*/
gchar **
gtk_source_language_get_style_ids (GtkSourceLanguage *language)
{
g_return_val_if_fail (GTK_IS_SOURCE_LANGUAGE (language), NULL);
g_return_val_if_fail (language->priv->id != NULL, NULL);
if (!force_styles (language))
return NULL;
return get_style_ids (language);
}
static GtkSourceStyleInfo *
get_style_info (GtkSourceLanguage *language, const char *style_id)
{
GtkSourceStyleInfo *info;
if (!force_styles (language))
return NULL;
g_return_val_if_fail (language->priv->styles != NULL, NULL);
info = g_hash_table_lookup (language->priv->styles, style_id);
return info;
}
/**
* gtk_source_language_get_style_name:
*
* @language: a #GtkSourceLanguage
* @style_id: a style ID
*
* Returns the name of the style with ID @style_id defined by this @language.
*
* Returns: the name of the style with ID @style_id defined by this @language or
* %NULL if the style has no name or there is no style with ID @style_id defined
* by this @language. The returned string is owned by the @language and must
* not be modified.
*/
const char *
gtk_source_language_get_style_name (GtkSourceLanguage *language,
const char *style_id)
{
GtkSourceStyleInfo *info;
g_return_val_if_fail (GTK_IS_SOURCE_LANGUAGE (language), NULL);
g_return_val_if_fail (language->priv->id != NULL, NULL);
g_return_val_if_fail (style_id != NULL, NULL);
info = get_style_info (language, style_id);
if (info == NULL)
return NULL;
return info->name;
}
/* Utility functions for GtkSourceStyleInfo */
GtkSourceStyleInfo *
_gtk_source_style_info_new (const gchar *name, const gchar *map_to)
{
GtkSourceStyleInfo *info = g_new0 (GtkSourceStyleInfo, 1);
info->name = g_strdup (name);
info->map_to = g_strdup (map_to);
return info;
}
GtkSourceStyleInfo *
_gtk_source_style_info_copy (GtkSourceStyleInfo *info)
{
g_return_val_if_fail (info != NULL, NULL);
return _gtk_source_style_info_new (info->name, info->map_to);
}
void
_gtk_source_style_info_free (GtkSourceStyleInfo *info)
{
if (info == NULL)
return;
g_free (info->name);
g_free (info->map_to);
g_free (info);
}