gem-graph-client/libide/code/ide-file-settings.c

547 lines
17 KiB
C

/* ide-file-settings.c
*
* Copyright 2015-2019 Christian Hergert <christian@hergert.me>
*
* 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 3 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "ide-file-settings"
#include "config.h"
#include <glib/gi18n.h>
#include <gtksourceview/gtksource.h>
#include "ide-code-enums.h"
#include "ide-file-settings.h"
/*
* WARNING: This file heavily uses XMACROS.
*
* XMACROS are not as difficult as you might imagine. It's basically just an
* inverstion of macros. We have a defs file (in this case
* ide-file-settings.defs) which defines information we need about properties.
* Then we define the macro called from that defs file to do something we need,
* then include the .defs file.
*
* We do that over and over again until we have all the aspects of the object
* defined.
*/
typedef struct
{
GPtrArray *children;
GFile *file;
const gchar *language;
guint unsettled_count;
#define IDE_FILE_SETTINGS_PROPERTY(_1, name, field_type, _3, _pname, _4, _5, _6) \
field_type name;
#include "ide-file-settings.defs"
#undef IDE_FILE_SETTINGS_PROPERTY
#define IDE_FILE_SETTINGS_PROPERTY(_1, name, field_type, _3, _pname, _4, _5, _6) \
guint name##_set : 1;
#include "ide-file-settings.defs"
#undef IDE_FILE_SETTINGS_PROPERTY
} IdeFileSettingsPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (IdeFileSettings, ide_file_settings, IDE_TYPE_OBJECT)
enum {
PROP_0,
PROP_FILE,
PROP_LANGUAGE,
PROP_SETTLED,
#define IDE_FILE_SETTINGS_PROPERTY(NAME, _1, _2, _3, _pname, _4, _5, _6) \
PROP_##NAME, \
PROP_##NAME##_SET,
#include "ide-file-settings.defs"
#undef IDE_FILE_SETTINGS_PROPERTY
LAST_PROP
};
static GParamSpec *properties [LAST_PROP];
#define IDE_FILE_SETTINGS_PROPERTY(_1, name, _2, ret_type, _pname, _3, _4, _5) \
ret_type ide_file_settings_get_##name (IdeFileSettings *self) \
{ \
IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self); \
g_return_val_if_fail (IDE_IS_FILE_SETTINGS (self), (ret_type)0); \
if (!ide_file_settings_get_##name##_set (self) && priv->children != NULL) \
{ \
for (guint i = 0; i < priv->children->len; i++) \
{ \
IdeFileSettings *child = g_ptr_array_index (priv->children, i); \
if (ide_file_settings_get_##name##_set (child)) \
return ide_file_settings_get_##name (child); \
} \
} \
return priv->name; \
}
# include "ide-file-settings.defs"
#undef IDE_FILE_SETTINGS_PROPERTY
#define IDE_FILE_SETTINGS_PROPERTY(_1, name, field_name, ret_type, _pname, _3, _4, _5) \
gboolean ide_file_settings_get_##name##_set (IdeFileSettings *self) \
{ \
IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self); \
g_return_val_if_fail (IDE_IS_FILE_SETTINGS (self), FALSE); \
return priv->name##_set; \
}
# include "ide-file-settings.defs"
#undef IDE_FILE_SETTINGS_PROPERTY
#define IDE_FILE_SETTINGS_PROPERTY(NAME, name, _1, ret_type, _pname, _3, assign_stmt, _4) \
void ide_file_settings_set_##name (IdeFileSettings *self, \
ret_type name) \
{ \
IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self); \
g_return_if_fail (IDE_IS_FILE_SETTINGS (self)); \
G_STMT_START { assign_stmt } G_STMT_END; \
priv->name##_set = TRUE; \
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_##NAME]); \
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_##NAME##_SET]); \
}
# include "ide-file-settings.defs"
#undef IDE_FILE_SETTINGS_PROPERTY
#define IDE_FILE_SETTINGS_PROPERTY(NAME, name, _1, _2, _pname, _3, _4, _5) \
void ide_file_settings_set_##name##_set (IdeFileSettings *self, \
gboolean name##_set) \
{ \
IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self); \
g_return_if_fail (IDE_IS_FILE_SETTINGS (self)); \
priv->name##_set = !!name##_set; \
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_##NAME##_SET]); \
}
# include "ide-file-settings.defs"
#undef IDE_FILE_SETTINGS_PROPERTY
/**
* ide_file_settings_get_file:
* @self: An #IdeFileSettings.
*
* Retrieves the underlying file that @self refers to.
*
* This may be used by #IdeFileSettings implementations to discover additional
* information about the settings. For example, a modeline parser might load
* some portion of the file looking for modelines. An editorconfig
* implementation might look for ".editorconfig" files.
*
* Returns: (transfer none): An #IdeFile.
*
* Since: 3.32
*/
GFile *
ide_file_settings_get_file (IdeFileSettings *self)
{
IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
g_return_val_if_fail (IDE_IS_FILE_SETTINGS (self), NULL);
return priv->file;
}
static void
ide_file_settings_set_file (IdeFileSettings *self,
GFile *file)
{
IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
g_return_if_fail (IDE_IS_FILE_SETTINGS (self));
g_return_if_fail (G_IS_FILE (file));
g_return_if_fail (priv->file == NULL);
priv->file = g_object_ref (file);
}
/**
* ide_file_settings_get_language:
* @self: a #IdeFileSettings
*
* If the language for file settings is known up-front, this will indicate
* the language identifier known to GtkSourceView such as "c" or "sh".
*
* Returns: (nullable): a string containing the language id or %NULL
*
* Since: 3.32
*/
const gchar *
ide_file_settings_get_language (IdeFileSettings *self)
{
IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
g_return_val_if_fail (IDE_IS_FILE_SETTINGS (self), NULL);
return priv->language;
}
static void
ide_file_settings_set_language (IdeFileSettings *self,
const gchar *language)
{
IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
g_return_if_fail (IDE_IS_FILE_SETTINGS (self));
priv->language = g_intern_string (language);
}
/**
* ide_file_settings_get_settled:
* @self: An #IdeFileSettings.
*
* Gets the #IdeFileSettings:settled property.
*
* This property is %TRUE when all of the children file settings have completed loading.
*
* Some file setting implementations require that various I/O be performed on disk in
* the background. This property will change to %TRUE when all of the settings have
* been loaded.
*
* Normally, this is not a problem, since the editor will respond to changes and update them
* accordingly. However, if you are writing a tool that prints the file settings
* (such as ide-list-file-settings), you probably want to wait until the values have
* settled.
*
* Returns: %TRUE if all the settings have loaded.
*
* Since: 3.32
*/
gboolean
ide_file_settings_get_settled (IdeFileSettings *self)
{
IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
g_return_val_if_fail (IDE_IS_FILE_SETTINGS (self), FALSE);
return (priv->unsettled_count == 0);
}
static gchar *
ide_file_settings_repr (IdeObject *object)
{
IdeFileSettings *self = (IdeFileSettings *)object;
IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
if (priv->file != NULL)
{
g_autofree gchar *uri = NULL;
if (g_file_is_native (priv->file))
return g_strdup_printf ("%s path=\"%s\"",
G_OBJECT_TYPE_NAME (self),
g_file_peek_path (priv->file));
uri = g_file_get_uri (priv->file);
return g_strdup_printf ("%s uri=\"%s\"", G_OBJECT_TYPE_NAME (self), uri);
}
return IDE_OBJECT_CLASS (ide_file_settings_parent_class)->repr (object);
}
static void
ide_file_settings_destroy (IdeObject *object)
{
IdeFileSettings *self = (IdeFileSettings *)object;
IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
g_clear_pointer (&priv->children, g_ptr_array_unref);
g_clear_pointer (&priv->encoding, g_free);
g_clear_object (&priv->file);
IDE_OBJECT_CLASS (ide_file_settings_parent_class)->destroy (object);
}
static void
ide_file_settings_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
IdeFileSettings *self = IDE_FILE_SETTINGS (object);
switch (prop_id)
{
case PROP_FILE:
g_value_set_object (value, ide_file_settings_get_file (self));
break;
case PROP_LANGUAGE:
g_value_set_static_string (value, ide_file_settings_get_language (self));
break;
case PROP_SETTLED:
g_value_set_boolean (value, ide_file_settings_get_settled (self));
break;
#define IDE_FILE_SETTINGS_PROPERTY(NAME, name, _2, _3, _4, _5, _6, value_type) \
case PROP_##NAME: \
g_value_set_##value_type (value, ide_file_settings_get_##name (self)); \
break;
# include "ide-file-settings.defs"
#undef IDE_FILE_SETTINGS_PROPERTY
#define IDE_FILE_SETTINGS_PROPERTY(NAME, name, _1, _2, _pname, _3, _4, _5) \
case PROP_##NAME##_SET: \
g_value_set_boolean (value, ide_file_settings_get_##name##_set (self)); \
break;
# include "ide-file-settings.defs"
#undef IDE_FILE_SETTINGS_PROPERTY
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
ide_file_settings_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
IdeFileSettings *self = IDE_FILE_SETTINGS (object);
switch (prop_id)
{
case PROP_FILE:
ide_file_settings_set_file (self, g_value_get_object (value));
break;
case PROP_LANGUAGE:
ide_file_settings_set_language (self, g_value_get_string (value));
break;
#define IDE_FILE_SETTINGS_PROPERTY(NAME, name, _2, _3, _4, _5, _6, value_type) \
case PROP_##NAME: \
ide_file_settings_set_##name (self, g_value_get_##value_type (value)); \
break;
# include "ide-file-settings.defs"
#undef IDE_FILE_SETTINGS_PROPERTY
#define IDE_FILE_SETTINGS_PROPERTY(NAME, name, _1, _2, _pname, _3, _4, _5) \
case PROP_##NAME##_SET: \
ide_file_settings_set_##name##_set (self, g_value_get_boolean (value)); \
break;
# include "ide-file-settings.defs"
#undef IDE_FILE_SETTINGS_PROPERTY
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
ide_file_settings_class_init (IdeFileSettingsClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
IdeObjectClass *i_object_class = IDE_OBJECT_CLASS (klass);
object_class->get_property = ide_file_settings_get_property;
object_class->set_property = ide_file_settings_set_property;
i_object_class->destroy = ide_file_settings_destroy;
i_object_class->repr = ide_file_settings_repr;
properties [PROP_FILE] =
g_param_spec_object ("file",
"File",
"The GFile the settings represent",
G_TYPE_FILE,
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
properties [PROP_LANGUAGE] =
g_param_spec_string ("language",
"Langauge",
"The language the settings represent",
NULL,
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
properties [PROP_SETTLED] =
g_param_spec_boolean ("settled",
"Settled",
"If the file settings implementations have settled",
FALSE,
(G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
#define IDE_FILE_SETTINGS_PROPERTY(NAME, name, _1, _2, _pname, pspec, _4, _5) \
properties [PROP_##NAME] = pspec;
# include "ide-file-settings.defs"
#undef IDE_FILE_SETTINGS_PROPERTY
#define IDE_FILE_SETTINGS_PROPERTY(NAME, name, _1, _2, _pname, pspec, _4, _5) \
properties [PROP_##NAME##_SET] = \
g_param_spec_boolean (_pname"-set", \
_pname"-set", \
"If IdeFileSettings:"_pname" is set.", \
FALSE, \
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
# include "ide-file-settings.defs"
#undef IDE_FILE_SETTINGS_PROPERTY
g_object_class_install_properties (object_class, LAST_PROP, properties);
}
static void
ide_file_settings_init (IdeFileSettings *self)
{
IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
priv->indent_style = IDE_INDENT_STYLE_SPACES;
priv->indent_width = -1;
priv->insert_trailing_newline = TRUE;
priv->newline_type = GTK_SOURCE_NEWLINE_TYPE_LF;
priv->right_margin_position = 80;
priv->tab_width = 8;
priv->trim_trailing_whitespace = TRUE;
}
static void
ide_file_settings_child_notify (IdeFileSettings *self,
GParamSpec *pspec,
IdeFileSettings *child)
{
g_assert (IDE_IS_FILE_SETTINGS (self));
g_assert (pspec != NULL);
g_assert (IDE_IS_FILE_SETTINGS (child));
if (pspec->owner_type == IDE_TYPE_FILE_SETTINGS)
g_object_notify_by_pspec (G_OBJECT (self), pspec);
}
static void
_ide_file_settings_append (IdeFileSettings *self,
IdeFileSettings *child)
{
IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
g_assert (IDE_IS_FILE_SETTINGS (self));
g_assert (IDE_IS_FILE_SETTINGS (child));
g_assert (self != child);
g_signal_connect_object (child,
"notify",
G_CALLBACK (ide_file_settings_child_notify),
self,
G_CONNECT_SWAPPED);
if (priv->children == NULL)
priv->children = g_ptr_array_new_with_free_func (g_object_unref);
g_ptr_array_add (priv->children, g_object_ref (child));
}
static void
ide_file_settings__init_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
g_autoptr(IdeFileSettings) self = user_data;
IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
GAsyncInitable *initable = (GAsyncInitable *)object;
g_autoptr(GError) error = NULL;
g_assert (IDE_IS_FILE_SETTINGS (self));
g_assert (G_IS_ASYNC_INITABLE (initable));
if (!g_async_initable_init_finish (initable, result, &error))
{
if (!ide_error_ignore (error))
g_warning ("%s", error->message);
}
if (--priv->unsettled_count == 0)
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SETTLED]);
}
IdeFileSettings *
ide_file_settings_new (IdeObject *parent,
GFile *file,
const gchar *language)
{
IdeFileSettingsPrivate *priv;
GIOExtensionPoint *extension_point;
IdeFileSettings *ret;
GList *list;
g_return_val_if_fail (G_IS_FILE (file), NULL);
g_return_val_if_fail (IDE_IS_OBJECT (parent), NULL);
ret = g_object_new (IDE_TYPE_FILE_SETTINGS,
"file", file,
"language", language,
NULL);
priv = ide_file_settings_get_instance_private (ret);
ide_object_append (parent, IDE_OBJECT (ret));
extension_point = g_io_extension_point_lookup (IDE_FILE_SETTINGS_EXTENSION_POINT);
list = g_io_extension_point_get_extensions (extension_point);
/*
* Don't allow our unsettled count to hit zero until we are finished.
*/
priv->unsettled_count++;
for (; list; list = list->next)
{
GIOExtension *extension = list->data;
g_autoptr(IdeFileSettings) child = NULL;
GType gtype;
gtype = g_io_extension_get_type (extension);
if (!g_type_is_a (gtype, IDE_TYPE_FILE_SETTINGS))
{
g_warning ("%s is not an IdeFileSettings", g_type_name (gtype));
continue;
}
child = g_object_new (gtype,
"file", file,
"language", language,
NULL);
ide_object_append (IDE_OBJECT (ret), IDE_OBJECT (child));
if (G_IS_INITABLE (child))
{
g_autoptr(GError) error = NULL;
if (!g_initable_init (G_INITABLE (child), NULL, &error))
{
if (!ide_error_ignore (error))
g_warning ("%s", error->message);
}
}
else if (G_IS_ASYNC_INITABLE (child))
{
priv->unsettled_count++;
g_async_initable_init_async (G_ASYNC_INITABLE (child),
G_PRIORITY_DEFAULT,
NULL,
ide_file_settings__init_cb,
g_object_ref (ret));
}
_ide_file_settings_append (ret, child);
}
priv->unsettled_count--;
return ret;
}