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