1 /* ide-file-settings.c
2  *
3  * Copyright 2015-2019 Christian Hergert <christian@hergert.me>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  * SPDX-License-Identifier: GPL-3.0-or-later
19  */
20 
21 #define G_LOG_DOMAIN "ide-file-settings"
22 
23 #include "config.h"
24 
25 #include <glib/gi18n.h>
26 #include <gtksourceview/gtksource.h>
27 
28 #include "ide-code-enums.h"
29 #include "ide-file-settings.h"
30 
31 /*
32  * WARNING: This file heavily uses XMACROS.
33  *
34  * XMACROS are not as difficult as you might imagine. It's basically just an
35  * inverstion of macros. We have a defs file (in this case
36  * ide-file-settings.defs) which defines information we need about properties.
37  * Then we define the macro called from that defs file to do something we need,
38  * then include the .defs file.
39  *
40  * We do that over and over again until we have all the aspects of the object
41  * defined.
42  */
43 
44 typedef struct
45 {
46   GPtrArray   *children;
47   GFile       *file;
48   const gchar *language;
49   guint        unsettled_count;
50 
51 #define IDE_FILE_SETTINGS_PROPERTY(_1, name, field_type, _3, _pname, _4, _5, _6) \
52   field_type name;
53 #include "ide-file-settings.defs"
54 #undef IDE_FILE_SETTINGS_PROPERTY
55 
56 #define IDE_FILE_SETTINGS_PROPERTY(_1, name, field_type, _3, _pname, _4, _5, _6) \
57   guint name##_set : 1;
58 #include "ide-file-settings.defs"
59 #undef IDE_FILE_SETTINGS_PROPERTY
60 } IdeFileSettingsPrivate;
61 
62 G_DEFINE_TYPE_WITH_PRIVATE (IdeFileSettings, ide_file_settings, IDE_TYPE_OBJECT)
63 
64 enum {
65   PROP_0,
66   PROP_FILE,
67   PROP_LANGUAGE,
68   PROP_SETTLED,
69 #define IDE_FILE_SETTINGS_PROPERTY(NAME, _1, _2, _3, _pname, _4, _5, _6) \
70   PROP_##NAME, \
71   PROP_##NAME##_SET,
72 #include "ide-file-settings.defs"
73 #undef IDE_FILE_SETTINGS_PROPERTY
74   LAST_PROP
75 };
76 
77 static GParamSpec *properties [LAST_PROP];
78 
79 #define IDE_FILE_SETTINGS_PROPERTY(_1, name, _2, ret_type, _pname, _3, _4, _5) \
80 ret_type ide_file_settings_get_##name (IdeFileSettings *self) \
81 { \
82   IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self); \
83   g_return_val_if_fail (IDE_IS_FILE_SETTINGS (self), (ret_type)0); \
84   if (!ide_file_settings_get_##name##_set (self) && priv->children != NULL) \
85     { \
86       for (guint i = 0; i < priv->children->len; i++) \
87         { \
88           IdeFileSettings *child = g_ptr_array_index (priv->children, i); \
89           if (ide_file_settings_get_##name##_set (child)) \
90             return ide_file_settings_get_##name (child); \
91         } \
92     } \
93   return priv->name; \
94 }
95 # include "ide-file-settings.defs"
96 #undef IDE_FILE_SETTINGS_PROPERTY
97 
98 #define IDE_FILE_SETTINGS_PROPERTY(_1, name, field_name, ret_type, _pname, _3, _4, _5) \
99 gboolean ide_file_settings_get_##name##_set (IdeFileSettings *self) \
100 { \
101   IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self); \
102   g_return_val_if_fail (IDE_IS_FILE_SETTINGS (self), FALSE); \
103   return priv->name##_set; \
104 }
105 # include "ide-file-settings.defs"
106 #undef IDE_FILE_SETTINGS_PROPERTY
107 
108 #define IDE_FILE_SETTINGS_PROPERTY(NAME, name, _1, ret_type, _pname, _3, assign_stmt, _4) \
109 void ide_file_settings_set_##name (IdeFileSettings *self, \
110                                    ret_type         name) \
111 { \
112   IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self); \
113   g_return_if_fail (IDE_IS_FILE_SETTINGS (self)); \
114   G_STMT_START { assign_stmt } G_STMT_END; \
115   priv->name##_set = TRUE; \
116   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_##NAME]); \
117   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_##NAME##_SET]); \
118 }
119 # include "ide-file-settings.defs"
120 #undef IDE_FILE_SETTINGS_PROPERTY
121 
122 #define IDE_FILE_SETTINGS_PROPERTY(NAME, name, _1, _2, _pname, _3, _4, _5) \
123 void ide_file_settings_set_##name##_set (IdeFileSettings *self, \
124                                          gboolean         name##_set) \
125 { \
126   IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self); \
127   g_return_if_fail (IDE_IS_FILE_SETTINGS (self)); \
128   priv->name##_set = !!name##_set; \
129   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_##NAME##_SET]); \
130 }
131 # include "ide-file-settings.defs"
132 #undef IDE_FILE_SETTINGS_PROPERTY
133 
134 /**
135  * ide_file_settings_get_file:
136  * @self: An #IdeFileSettings.
137  *
138  * Retrieves the underlying file that @self refers to.
139  *
140  * This may be used by #IdeFileSettings implementations to discover additional
141  * information about the settings. For example, a modeline parser might load
142  * some portion of the file looking for modelines. An editorconfig
143  * implementation might look for ".editorconfig" files.
144  *
145  * Returns: (transfer none): An #IdeFile.
146  *
147  * Since: 3.32
148  */
149 GFile *
ide_file_settings_get_file(IdeFileSettings * self)150 ide_file_settings_get_file (IdeFileSettings *self)
151 {
152   IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
153 
154   g_return_val_if_fail (IDE_IS_FILE_SETTINGS (self), NULL);
155 
156   return priv->file;
157 }
158 
159 static void
ide_file_settings_set_file(IdeFileSettings * self,GFile * file)160 ide_file_settings_set_file (IdeFileSettings *self,
161                             GFile           *file)
162 {
163   IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
164 
165   g_return_if_fail (IDE_IS_FILE_SETTINGS (self));
166   g_return_if_fail (G_IS_FILE (file));
167   g_return_if_fail (priv->file == NULL);
168 
169   priv->file = g_object_ref (file);
170 }
171 
172 /**
173  * ide_file_settings_get_language:
174  * @self: a #IdeFileSettings
175  *
176  * If the language for file settings is known up-front, this will indicate
177  * the language identifier known to GtkSourceView such as "c" or "sh".
178  *
179  * Returns: (nullable): a string containing the language id or %NULL
180  *
181  * Since: 3.32
182  */
183 const gchar *
ide_file_settings_get_language(IdeFileSettings * self)184 ide_file_settings_get_language (IdeFileSettings *self)
185 {
186   IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
187 
188   g_return_val_if_fail (IDE_IS_FILE_SETTINGS (self), NULL);
189 
190   return priv->language;
191 }
192 
193 static void
ide_file_settings_set_language(IdeFileSettings * self,const gchar * language)194 ide_file_settings_set_language (IdeFileSettings *self,
195                                 const gchar     *language)
196 {
197   IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
198 
199   g_return_if_fail (IDE_IS_FILE_SETTINGS (self));
200 
201   priv->language = g_intern_string (language);
202 }
203 
204 /**
205  * ide_file_settings_get_settled:
206  * @self: An #IdeFileSettings.
207  *
208  * Gets the #IdeFileSettings:settled property.
209  *
210  * This property is %TRUE when all of the children file settings have completed loading.
211  *
212  * Some file setting implementations require that various I/O be performed on disk in
213  * the background. This property will change to %TRUE when all of the settings have
214  * been loaded.
215  *
216  * Normally, this is not a problem, since the editor will respond to changes and update them
217  * accordingly. However, if you are writing a tool that prints the file settings
218  * (such as ide-list-file-settings), you probably want to wait until the values have
219  * settled.
220  *
221  * Returns: %TRUE if all the settings have loaded.
222  *
223  * Since: 3.32
224  */
225 gboolean
ide_file_settings_get_settled(IdeFileSettings * self)226 ide_file_settings_get_settled (IdeFileSettings *self)
227 {
228   IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
229 
230   g_return_val_if_fail (IDE_IS_FILE_SETTINGS (self), FALSE);
231 
232   return (priv->unsettled_count == 0);
233 }
234 
235 static gchar *
ide_file_settings_repr(IdeObject * object)236 ide_file_settings_repr (IdeObject *object)
237 {
238   IdeFileSettings *self = (IdeFileSettings *)object;
239   IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
240 
241   if (priv->file != NULL)
242     {
243       g_autofree gchar *uri = NULL;
244 
245       if (g_file_is_native (priv->file))
246         return g_strdup_printf ("%s path=\"%s\"",
247                                 G_OBJECT_TYPE_NAME (self),
248                                 g_file_peek_path (priv->file));
249 
250       uri = g_file_get_uri (priv->file);
251       return g_strdup_printf ("%s uri=\"%s\"", G_OBJECT_TYPE_NAME (self), uri);
252     }
253 
254   return IDE_OBJECT_CLASS (ide_file_settings_parent_class)->repr (object);
255 }
256 
257 static void
ide_file_settings_destroy(IdeObject * object)258 ide_file_settings_destroy (IdeObject *object)
259 {
260   IdeFileSettings *self = (IdeFileSettings *)object;
261   IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
262 
263   g_clear_pointer (&priv->children, g_ptr_array_unref);
264   g_clear_pointer (&priv->encoding, g_free);
265   g_clear_object (&priv->file);
266 
267   IDE_OBJECT_CLASS (ide_file_settings_parent_class)->destroy (object);
268 }
269 
270 static void
ide_file_settings_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)271 ide_file_settings_get_property (GObject    *object,
272                                 guint       prop_id,
273                                 GValue     *value,
274                                 GParamSpec *pspec)
275 {
276   IdeFileSettings *self = IDE_FILE_SETTINGS (object);
277 
278   switch (prop_id)
279     {
280     case PROP_FILE:
281       g_value_set_object (value, ide_file_settings_get_file (self));
282       break;
283 
284     case PROP_LANGUAGE:
285       g_value_set_static_string (value, ide_file_settings_get_language (self));
286       break;
287 
288     case PROP_SETTLED:
289       g_value_set_boolean (value, ide_file_settings_get_settled (self));
290       break;
291 
292 #define IDE_FILE_SETTINGS_PROPERTY(NAME, name, _2, _3, _4, _5, _6, value_type) \
293     case PROP_##NAME: \
294       g_value_set_##value_type (value, ide_file_settings_get_##name (self)); \
295       break;
296 # include "ide-file-settings.defs"
297 #undef IDE_FILE_SETTINGS_PROPERTY
298 
299 #define IDE_FILE_SETTINGS_PROPERTY(NAME, name, _1, _2, _pname, _3, _4, _5) \
300     case PROP_##NAME##_SET: \
301       g_value_set_boolean (value, ide_file_settings_get_##name##_set (self)); \
302       break;
303 # include "ide-file-settings.defs"
304 #undef IDE_FILE_SETTINGS_PROPERTY
305 
306     default:
307       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
308     }
309 }
310 
311 static void
ide_file_settings_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)312 ide_file_settings_set_property (GObject      *object,
313                                 guint         prop_id,
314                                 const GValue *value,
315                                 GParamSpec   *pspec)
316 {
317   IdeFileSettings *self = IDE_FILE_SETTINGS (object);
318 
319   switch (prop_id)
320     {
321     case PROP_FILE:
322       ide_file_settings_set_file (self, g_value_get_object (value));
323       break;
324 
325     case PROP_LANGUAGE:
326       ide_file_settings_set_language (self, g_value_get_string (value));
327       break;
328 
329 #define IDE_FILE_SETTINGS_PROPERTY(NAME, name, _2, _3, _4, _5, _6, value_type) \
330     case PROP_##NAME: \
331       ide_file_settings_set_##name (self, g_value_get_##value_type (value)); \
332       break;
333 # include "ide-file-settings.defs"
334 #undef IDE_FILE_SETTINGS_PROPERTY
335 
336 #define IDE_FILE_SETTINGS_PROPERTY(NAME, name, _1, _2, _pname, _3, _4, _5) \
337     case PROP_##NAME##_SET: \
338       ide_file_settings_set_##name##_set (self, g_value_get_boolean (value)); \
339       break;
340 # include "ide-file-settings.defs"
341 #undef IDE_FILE_SETTINGS_PROPERTY
342 
343     default:
344       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
345     }
346 }
347 
348 static void
ide_file_settings_class_init(IdeFileSettingsClass * klass)349 ide_file_settings_class_init (IdeFileSettingsClass *klass)
350 {
351   GObjectClass *object_class = G_OBJECT_CLASS (klass);
352   IdeObjectClass *i_object_class = IDE_OBJECT_CLASS (klass);
353 
354   object_class->get_property = ide_file_settings_get_property;
355   object_class->set_property = ide_file_settings_set_property;
356 
357   i_object_class->destroy = ide_file_settings_destroy;
358   i_object_class->repr = ide_file_settings_repr;
359 
360   properties [PROP_FILE] =
361     g_param_spec_object ("file",
362                          "File",
363                          "The GFile the settings represent",
364                          G_TYPE_FILE,
365                          (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
366 
367   properties [PROP_LANGUAGE] =
368     g_param_spec_string ("language",
369                          "Langauge",
370                          "The language the settings represent",
371                          NULL,
372                          (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
373 
374   properties [PROP_SETTLED] =
375     g_param_spec_boolean ("settled",
376                           "Settled",
377                           "If the file settings implementations have settled",
378                           FALSE,
379                           (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
380 
381 #define IDE_FILE_SETTINGS_PROPERTY(NAME, name, _1, _2, _pname, pspec, _4, _5) \
382   properties [PROP_##NAME] = pspec;
383 # include "ide-file-settings.defs"
384 #undef IDE_FILE_SETTINGS_PROPERTY
385 
386 #define IDE_FILE_SETTINGS_PROPERTY(NAME, name, _1, _2, _pname, pspec, _4, _5) \
387   properties [PROP_##NAME##_SET] = \
388     g_param_spec_boolean (_pname"-set", \
389                           _pname"-set", \
390                           "If IdeFileSettings:"_pname" is set.", \
391                           FALSE, \
392                           (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
393 # include "ide-file-settings.defs"
394 #undef IDE_FILE_SETTINGS_PROPERTY
395 
396   g_object_class_install_properties (object_class, LAST_PROP, properties);
397 }
398 
399 static void
ide_file_settings_init(IdeFileSettings * self)400 ide_file_settings_init (IdeFileSettings *self)
401 {
402   IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
403 
404   priv->indent_style = IDE_INDENT_STYLE_SPACES;
405   priv->indent_width = -1;
406   priv->insert_trailing_newline = TRUE;
407   priv->newline_type = GTK_SOURCE_NEWLINE_TYPE_LF;
408   priv->right_margin_position = 80;
409   priv->tab_width = 8;
410   priv->trim_trailing_whitespace = TRUE;
411 }
412 
413 static void
ide_file_settings_child_notify(IdeFileSettings * self,GParamSpec * pspec,IdeFileSettings * child)414 ide_file_settings_child_notify (IdeFileSettings *self,
415                                 GParamSpec      *pspec,
416                                 IdeFileSettings *child)
417 {
418   g_assert (IDE_IS_FILE_SETTINGS (self));
419   g_assert (pspec != NULL);
420   g_assert (IDE_IS_FILE_SETTINGS (child));
421 
422   if (pspec->owner_type == IDE_TYPE_FILE_SETTINGS)
423     g_object_notify_by_pspec (G_OBJECT (self), pspec);
424 }
425 
426 static void
_ide_file_settings_append(IdeFileSettings * self,IdeFileSettings * child)427 _ide_file_settings_append (IdeFileSettings *self,
428                            IdeFileSettings *child)
429 {
430   IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
431 
432   g_assert (IDE_IS_FILE_SETTINGS (self));
433   g_assert (IDE_IS_FILE_SETTINGS (child));
434   g_assert (self != child);
435 
436   g_signal_connect_object (child,
437                            "notify",
438                            G_CALLBACK (ide_file_settings_child_notify),
439                            self,
440                            G_CONNECT_SWAPPED);
441 
442   if (priv->children == NULL)
443     priv->children = g_ptr_array_new_with_free_func (g_object_unref);
444 
445   g_ptr_array_add (priv->children, g_object_ref (child));
446 }
447 
448 static void
ide_file_settings__init_cb(GObject * object,GAsyncResult * result,gpointer user_data)449 ide_file_settings__init_cb (GObject      *object,
450                             GAsyncResult *result,
451                             gpointer      user_data)
452 {
453   g_autoptr(IdeFileSettings) self = user_data;
454   IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
455   GAsyncInitable *initable = (GAsyncInitable *)object;
456   g_autoptr(GError) error = NULL;
457 
458   g_assert (IDE_IS_FILE_SETTINGS (self));
459   g_assert (G_IS_ASYNC_INITABLE (initable));
460 
461   if (!g_async_initable_init_finish (initable, result, &error))
462     {
463       if (!ide_error_ignore (error))
464         g_warning ("%s", error->message);
465     }
466 
467   if (--priv->unsettled_count == 0)
468     g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SETTLED]);
469 }
470 
471 IdeFileSettings *
ide_file_settings_new(IdeObject * parent,GFile * file,const gchar * language)472 ide_file_settings_new (IdeObject   *parent,
473                        GFile       *file,
474                        const gchar *language)
475 {
476   IdeFileSettingsPrivate *priv;
477   GIOExtensionPoint *extension_point;
478   IdeFileSettings *ret;
479   GList *list;
480 
481   g_return_val_if_fail (G_IS_FILE (file), NULL);
482   g_return_val_if_fail (IDE_IS_OBJECT (parent), NULL);
483 
484   ret = g_object_new (IDE_TYPE_FILE_SETTINGS,
485                       "file", file,
486                       "language", language,
487                       NULL);
488   priv = ide_file_settings_get_instance_private (ret);
489 
490   ide_object_append (parent, IDE_OBJECT (ret));
491 
492   extension_point = g_io_extension_point_lookup (IDE_FILE_SETTINGS_EXTENSION_POINT);
493   list = g_io_extension_point_get_extensions (extension_point);
494 
495   /*
496    * Don't allow our unsettled count to hit zero until we are finished.
497    */
498   priv->unsettled_count++;
499 
500   for (; list; list = list->next)
501     {
502       GIOExtension *extension = list->data;
503       g_autoptr(IdeFileSettings) child = NULL;
504       GType gtype;
505 
506       gtype = g_io_extension_get_type (extension);
507 
508       if (!g_type_is_a (gtype, IDE_TYPE_FILE_SETTINGS))
509         {
510           g_warning ("%s is not an IdeFileSettings", g_type_name (gtype));
511           continue;
512         }
513 
514       child = g_object_new (gtype,
515                             "file", file,
516                             "language", language,
517                             NULL);
518       ide_object_append (IDE_OBJECT (ret), IDE_OBJECT (child));
519 
520       if (G_IS_INITABLE (child))
521         {
522           g_autoptr(GError) error = NULL;
523 
524           if (!g_initable_init (G_INITABLE (child), NULL, &error))
525             {
526               if (!ide_error_ignore (error))
527                 g_warning ("%s", error->message);
528             }
529         }
530       else if (G_IS_ASYNC_INITABLE (child))
531         {
532           priv->unsettled_count++;
533           g_async_initable_init_async (G_ASYNC_INITABLE (child),
534                                        G_PRIORITY_DEFAULT,
535                                        NULL,
536                                        ide_file_settings__init_cb,
537                                        g_object_ref (ret));
538         }
539 
540       _ide_file_settings_append (ret, child);
541     }
542 
543   priv->unsettled_count--;
544 
545   return ret;
546 }
547