1 /* ide-buffer-manager.c
2 *
3 * Copyright 2018-2019 Christian Hergert <chergert@redhat.com>
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-buffer-manager"
22
23 #include "config.h"
24
25 #include <glib/gi18n.h>
26 #include <libide-core.h>
27 #include <libide-threading.h>
28
29 #include "ide-buffer.h"
30 #include "ide-buffer-private.h"
31 #include "ide-buffer-manager.h"
32 #include "ide-doc-seq-private.h"
33 #include "ide-text-edit.h"
34 #include "ide-text-edit-private.h"
35 #include "ide-location.h"
36 #include "ide-range.h"
37
38 struct _IdeBufferManager
39 {
40 IdeObject parent_instance;
41 GHashTable *loading_tasks;
42 gssize max_file_size;
43 };
44
45 typedef struct
46 {
47 GPtrArray *buffers;
48 guint n_active;
49 guint had_failure : 1;
50 } SaveAll;
51
52 typedef struct
53 {
54 GPtrArray *edits;
55 GHashTable *buffers;
56 GHashTable *to_close;
57 guint n_active;
58 guint failed : 1;
59 } EditState;
60
61 typedef struct
62 {
63 GFile *file;
64 IdeBuffer *buffer;
65 } FindBuffer;
66
67 typedef struct
68 {
69 IdeBufferForeachFunc func;
70 gpointer user_data;
71 } Foreach;
72
73 static void list_model_iface_init (GListModelInterface *iface);
74
75 G_DEFINE_FINAL_TYPE_WITH_CODE (IdeBufferManager, ide_buffer_manager, IDE_TYPE_OBJECT,
76 G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
77
78 enum {
79 PROP_0,
80 PROP_MAX_FILE_SIZE,
81 N_PROPS
82 };
83
84 enum {
85 BUFFER_LOADED,
86 BUFFER_SAVED,
87 BUFFER_UNLOADED,
88 LOAD_BUFFER,
89 N_SIGNALS
90 };
91
92 static GParamSpec *properties [N_PROPS];
93 static guint signals [N_SIGNALS];
94
95 static void
edit_state_free(EditState * state)96 edit_state_free (EditState *state)
97 {
98 g_assert (IDE_IS_MAIN_THREAD ());
99
100 if (state != NULL)
101 {
102 g_clear_pointer (&state->edits, g_ptr_array_unref);
103 g_clear_pointer (&state->buffers, g_hash_table_unref);
104 g_clear_pointer (&state->to_close, g_hash_table_unref);
105 g_slice_free (EditState, state);
106 }
107 }
108
109 static void
save_all_free(SaveAll * state)110 save_all_free (SaveAll *state)
111 {
112 g_assert (state->n_active == 0);
113 g_clear_pointer (&state->buffers, g_ptr_array_unref);
114 g_slice_free (SaveAll, state);
115 }
116
117 static IdeBuffer *
ide_buffer_manager_create_buffer(IdeBufferManager * self,GFile * file,gboolean enable_addins,gboolean is_temporary)118 ide_buffer_manager_create_buffer (IdeBufferManager *self,
119 GFile *file,
120 gboolean enable_addins,
121 gboolean is_temporary)
122 {
123 g_autoptr(IdeBuffer) buffer = NULL;
124 g_autoptr(IdeObjectBox) box = NULL;
125
126 IDE_ENTRY;
127
128 g_assert (IDE_IS_MAIN_THREAD ());
129 g_assert (IDE_IS_BUFFER_MANAGER (self));
130
131 buffer = _ide_buffer_new (self, file, enable_addins, is_temporary);
132 box = ide_object_box_new (G_OBJECT (buffer));
133
134 ide_object_append (IDE_OBJECT (self), IDE_OBJECT (box));
135 _ide_buffer_attach (buffer, IDE_OBJECT (box));
136
137 IDE_RETURN (g_steal_pointer (&buffer));
138 }
139
140 static void
ide_buffer_manager_add(IdeObject * object,IdeObject * sibling,IdeObject * child,IdeObjectLocation location)141 ide_buffer_manager_add (IdeObject *object,
142 IdeObject *sibling,
143 IdeObject *child,
144 IdeObjectLocation location)
145 {
146 IdeBufferManager *self = (IdeBufferManager *)object;
147 g_autoptr(IdeBuffer) buffer = NULL;
148
149 g_assert (IDE_IS_MAIN_THREAD ());
150 g_assert (IDE_IS_BUFFER_MANAGER (self));
151 g_assert (IDE_IS_OBJECT (child));
152
153 if (!IDE_IS_OBJECT_BOX (child) ||
154 !IDE_IS_BUFFER ((buffer = ide_object_box_ref_object (IDE_OBJECT_BOX (child)))))
155 {
156 g_critical ("You may only add an IdeObjectBox of IdeBuffer to an IdeBufferManager");
157 return;
158 }
159
160 IDE_OBJECT_CLASS (ide_buffer_manager_parent_class)->add (object, sibling, child, location);
161 g_list_model_items_changed (G_LIST_MODEL (self), ide_object_get_position (child), 0, 1);
162 }
163
164 static void
ide_buffer_manager_remove(IdeObject * object,IdeObject * child)165 ide_buffer_manager_remove (IdeObject *object,
166 IdeObject *child)
167 {
168 IdeBufferManager *self = (IdeBufferManager *)object;
169 g_autoptr(IdeBuffer) buffer = NULL;
170 guint position;
171
172 g_assert (IDE_IS_MAIN_THREAD ());
173 g_assert (IDE_IS_BUFFER_MANAGER (self));
174 g_assert (IDE_IS_OBJECT_BOX (child));
175
176 buffer = ide_object_box_ref_object (IDE_OBJECT_BOX (child));
177 g_signal_emit (self, signals [BUFFER_UNLOADED], 0, buffer);
178
179 position = ide_object_get_position (child);
180 IDE_OBJECT_CLASS (ide_buffer_manager_parent_class)->remove (object, child);
181 g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 0);
182 }
183
184 static void
ide_buffer_manager_destroy(IdeObject * object)185 ide_buffer_manager_destroy (IdeObject *object)
186 {
187 IdeBufferManager *self = (IdeBufferManager *)object;
188
189 IDE_ENTRY;
190
191 g_clear_pointer (&self->loading_tasks, g_hash_table_unref);
192
193 IDE_OBJECT_CLASS (ide_buffer_manager_parent_class)->destroy (object);
194
195 IDE_EXIT;
196 }
197
198 static void
ide_buffer_manager_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)199 ide_buffer_manager_get_property (GObject *object,
200 guint prop_id,
201 GValue *value,
202 GParamSpec *pspec)
203 {
204 IdeBufferManager *self = IDE_BUFFER_MANAGER (object);
205
206 switch (prop_id)
207 {
208 case PROP_MAX_FILE_SIZE:
209 g_value_set_int64 (value, ide_buffer_manager_get_max_file_size (self));
210 break;
211
212 default:
213 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
214 }
215 }
216
217 static void
ide_buffer_manager_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)218 ide_buffer_manager_set_property (GObject *object,
219 guint prop_id,
220 const GValue *value,
221 GParamSpec *pspec)
222 {
223 IdeBufferManager *self = IDE_BUFFER_MANAGER (object);
224
225 switch (prop_id)
226 {
227 case PROP_MAX_FILE_SIZE:
228 ide_buffer_manager_set_max_file_size (self, g_value_get_int64 (value));
229 break;
230
231 default:
232 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
233 }
234 }
235
236 static void
ide_buffer_manager_class_init(IdeBufferManagerClass * klass)237 ide_buffer_manager_class_init (IdeBufferManagerClass *klass)
238 {
239 IdeObjectClass *i_object_class = IDE_OBJECT_CLASS (klass);
240 GObjectClass *object_class = G_OBJECT_CLASS (klass);
241
242 object_class->get_property = ide_buffer_manager_get_property;
243 object_class->set_property = ide_buffer_manager_set_property;
244
245 i_object_class->add = ide_buffer_manager_add;
246 i_object_class->remove = ide_buffer_manager_remove;
247 i_object_class->destroy = ide_buffer_manager_destroy;
248
249 /**
250 * IdeBufferManager:max-file-size:
251 *
252 * The "max-file-size" property is the largest file size in bytes that
253 * Builder will attempt to load. Larger files will fail to load to help
254 * ensure that Builder's buffer manager does not attempt to load files that
255 * will slow the buffer management beyond usefulness.
256 *
257 * Since: 3.32
258 */
259 properties [PROP_MAX_FILE_SIZE] =
260 g_param_spec_int64 ("max-file-size",
261 "Max File Size",
262 "The max file size to load",
263 -1,
264 G_MAXINT64,
265 10L * 1024L * 1024L,
266 (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
267
268 g_object_class_install_properties (object_class, N_PROPS, properties);
269
270 /**
271 * IdeBufferManager:load-buffer:
272 * @self: an #IdeBufferManager
273 * @buffer: an #IdeBuffer
274 * @create_new_view: if the buffer requires a view to be created
275 *
276 * The "load-buffer" signal is emitted before a buffer is (re)loaded.
277 *
278 * Since: 3.32
279 */
280 signals [LOAD_BUFFER] =
281 g_signal_new ("load-buffer",
282 G_TYPE_FROM_CLASS (klass),
283 G_SIGNAL_RUN_LAST,
284 0,
285 NULL,
286 NULL,
287 NULL,
288 G_TYPE_NONE, 2, IDE_TYPE_BUFFER, G_TYPE_BOOLEAN);
289
290 /**
291 * IdeBufferManager::buffer-loaded:
292 * @self: an #IdeBufferManager
293 * @buffer: an #IdeBuffer
294 *
295 * The "buffer-loaded" signal is emitted when an #IdeBuffer has loaded
296 * a file from storage.
297 *
298 * Since: 3.32
299 */
300 signals [BUFFER_LOADED] =
301 g_signal_new ("buffer-loaded",
302 G_TYPE_FROM_CLASS (klass),
303 G_SIGNAL_RUN_LAST,
304 0,
305 NULL,
306 NULL,
307 g_cclosure_marshal_VOID__OBJECT,
308 G_TYPE_NONE, 1, IDE_TYPE_BUFFER);
309 g_signal_set_va_marshaller (signals [BUFFER_LOADED],
310 G_TYPE_FROM_CLASS (klass),
311 g_cclosure_marshal_VOID__OBJECTv);
312
313 /**
314 * IdeBufferManager::buffer-saved:
315 * @self: an #IdeBufferManager
316 * @buffer: an #IdeBuffer
317 *
318 * The "buffer-saved" signal is emitted when an #IdeBuffer has been saved
319 * to storage.
320 *
321 * Since: 3.32
322 */
323 signals [BUFFER_SAVED] =
324 g_signal_new ("buffer-saved",
325 G_TYPE_FROM_CLASS (klass),
326 G_SIGNAL_RUN_LAST,
327 0,
328 NULL,
329 NULL,
330 g_cclosure_marshal_VOID__OBJECT,
331 G_TYPE_NONE, 1, IDE_TYPE_BUFFER);
332 g_signal_set_va_marshaller (signals [BUFFER_SAVED],
333 G_TYPE_FROM_CLASS (klass),
334 g_cclosure_marshal_VOID__OBJECTv);
335
336 /**
337 * IdeBufferManager::buffer-unloaded:
338 * @self: an #IdeBufferManager
339 * @buffer: an #IdeBuffer
340 *
341 * The "buffer-unloaded" signal is emitted when an #IdeBuffer has been
342 * unloaded from the buffer manager.
343 *
344 * Since: 3.32
345 */
346 signals [BUFFER_UNLOADED] =
347 g_signal_new ("buffer-unloaded",
348 G_TYPE_FROM_CLASS (klass),
349 G_SIGNAL_RUN_LAST,
350 0,
351 NULL,
352 NULL,
353 g_cclosure_marshal_VOID__OBJECT,
354 G_TYPE_NONE, 1, IDE_TYPE_BUFFER);
355 g_signal_set_va_marshaller (signals [BUFFER_UNLOADED],
356 G_TYPE_FROM_CLASS (klass),
357 g_cclosure_marshal_VOID__OBJECTv);
358 }
359
360 static void
ide_buffer_manager_init(IdeBufferManager * self)361 ide_buffer_manager_init (IdeBufferManager *self)
362 {
363 IDE_ENTRY;
364
365 self->loading_tasks = g_hash_table_new_full (g_file_hash,
366 (GEqualFunc)g_file_equal,
367 g_object_unref,
368 g_object_unref);
369
370 IDE_EXIT;
371 }
372
373 static GFile *
ide_buffer_manager_next_temp_file(IdeBufferManager * self)374 ide_buffer_manager_next_temp_file (IdeBufferManager *self)
375 {
376 g_autoptr(IdeContext) context = NULL;
377 g_autoptr(GFile) workdir = NULL;
378 g_autoptr(GFile) ret = NULL;
379 g_autofree gchar *name = NULL;
380 guint doc_id;
381
382 IDE_ENTRY;
383
384 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
385 g_return_val_if_fail (IDE_IS_BUFFER_MANAGER (self), NULL);
386
387 context = IDE_CONTEXT (ide_object_ref_root (IDE_OBJECT (self)));
388 workdir = ide_context_ref_workdir (context);
389 doc_id = ide_doc_seq_acquire ();
390
391 /* translators: %u is replaced with an incrementing number */
392 name = g_strdup_printf (_("unsaved file %u"), doc_id);
393
394 ret = g_file_get_child (workdir, name);
395
396 IDE_RETURN (g_steal_pointer (&ret));
397 }
398
399 /**
400 * ide_buffer_manager_from_context:
401 *
402 * Gets the #IdeBufferManager for the #IdeContext.
403 *
404 * Returns: (transfer none): an #IdeBufferManager
405 *
406 * Since: 3.32
407 */
408 IdeBufferManager *
ide_buffer_manager_from_context(IdeContext * context)409 ide_buffer_manager_from_context (IdeContext *context)
410 {
411 IdeBufferManager *self;
412
413 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
414 g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
415
416 self = ide_context_peek_child_typed (context, IDE_TYPE_BUFFER_MANAGER);
417 g_return_val_if_fail (IDE_IS_BUFFER_MANAGER (self), NULL);
418 return self;
419 }
420
421 /**
422 * ide_buffer_manager_has_file:
423 * @self: an #IdeBufferManager
424 * @file: a #GFile
425 *
426 * Checks to see if a buffer has been loaded which contains the contents
427 * of @file.
428 *
429 * Returns: %TRUE if a buffer exists for @file
430 *
431 * Since: 3.32
432 */
433 gboolean
ide_buffer_manager_has_file(IdeBufferManager * self,GFile * file)434 ide_buffer_manager_has_file (IdeBufferManager *self,
435 GFile *file)
436 {
437 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
438 g_return_val_if_fail (IDE_IS_BUFFER_MANAGER (self), FALSE);
439 g_return_val_if_fail (G_IS_FILE (file), FALSE);
440
441 return ide_buffer_manager_find_buffer (self, file) != NULL;
442 }
443
444 static void
ide_buffer_manager_find_buffer_cb(IdeObject * object,FindBuffer * find)445 ide_buffer_manager_find_buffer_cb (IdeObject *object,
446 FindBuffer *find)
447 {
448 g_autoptr(IdeBuffer) buffer = NULL;
449
450 g_assert (IDE_IS_OBJECT_BOX (object));
451 g_assert (find != NULL);
452 g_assert (G_IS_FILE (find->file));
453
454 if (find->buffer != NULL)
455 return;
456
457 buffer = ide_object_box_ref_object (IDE_OBJECT_BOX (object));
458
459 /* We pass back a borrowed reference */
460 if (g_file_equal (find->file, ide_buffer_get_file (buffer)))
461 find->buffer = buffer;
462 }
463
464 /**
465 * ide_buffer_manager_find_buffer:
466 * @self: an #IdeBufferManager
467 * @file: a #GFile
468 *
469 * Locates the #IdeBuffer that matches #GFile, if any.
470 *
471 * Returns: (transfer full): an #IdeBuffer or %NULL
472 *
473 * Since: 3.32
474 */
475 IdeBuffer *
ide_buffer_manager_find_buffer(IdeBufferManager * self,GFile * file)476 ide_buffer_manager_find_buffer (IdeBufferManager *self,
477 GFile *file)
478 {
479 FindBuffer find = { file, NULL };
480
481 IDE_ENTRY;
482
483 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
484 g_return_val_if_fail (IDE_IS_BUFFER_MANAGER (self), NULL);
485 g_return_val_if_fail (G_IS_FILE (file), NULL);
486
487 ide_object_foreach (IDE_OBJECT (self),
488 (GFunc)ide_buffer_manager_find_buffer_cb,
489 &find);
490
491 IDE_RETURN (find.buffer);
492 }
493
494 /**
495 * ide_buffer_manager_get_max_file_size:
496 * @self: an #IdeBufferManager
497 *
498 * Gets the max file size that will be allowed to be loaded from disk.
499 * This is useful to protect Builder from files that would overload the
500 * various subsystems.
501 *
502 * Returns: the max file size in bytes or -1 for unlimited
503 *
504 * Since: 3.32
505 */
506 gssize
ide_buffer_manager_get_max_file_size(IdeBufferManager * self)507 ide_buffer_manager_get_max_file_size (IdeBufferManager *self)
508 {
509 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), 0);
510 g_return_val_if_fail (IDE_IS_BUFFER_MANAGER (self), 0);
511
512 return self->max_file_size;
513 }
514
515 /**
516 * ide_buffer_manager_set_max_size:
517 * @self: an #IdeBufferManager
518 * @max_file_size: the max file size in bytes or -1 for unlimited
519 *
520 * Sets the max file size that will be allowed to be loaded from disk.
521 * This is useful to protect Builder from files that would overload the
522 * various subsystems.
523 *
524 * Since: 3.32
525 */
526 void
ide_buffer_manager_set_max_file_size(IdeBufferManager * self,gssize max_file_size)527 ide_buffer_manager_set_max_file_size (IdeBufferManager *self,
528 gssize max_file_size)
529 {
530 g_return_if_fail (IDE_IS_MAIN_THREAD ());
531 g_return_if_fail (IDE_IS_BUFFER_MANAGER (self));
532 g_return_if_fail (max_file_size >= -1);
533
534 if (self->max_file_size != max_file_size)
535 {
536 self->max_file_size = max_file_size;
537 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MAX_FILE_SIZE]);
538 }
539 }
540
541 static void
ide_buffer_manager_load_file_cb(GObject * object,GAsyncResult * result,gpointer user_data)542 ide_buffer_manager_load_file_cb (GObject *object,
543 GAsyncResult *result,
544 gpointer user_data)
545 {
546 IdeBuffer *buffer = (IdeBuffer *)object;
547 g_autoptr(IdeTask) task = user_data;
548 g_autoptr(GError) error = NULL;
549 IdeBufferManager *self;
550 GFile *file;
551
552 IDE_ENTRY;
553
554 g_assert (IDE_IS_BUFFER (buffer));
555 g_assert (G_IS_ASYNC_RESULT (result));
556 g_assert (IDE_IS_TASK (task));
557
558 self = ide_task_get_source_object (task);
559 file = ide_task_get_task_data (task);
560
561 g_assert (IDE_IS_BUFFER_MANAGER (self));
562 g_assert (G_IS_FILE (file));
563
564 g_hash_table_remove (self->loading_tasks, file);
565
566 if (!_ide_buffer_load_file_finish (buffer, result, &error))
567 ide_task_return_error (task, g_steal_pointer (&error));
568 else
569 ide_task_return_object (task, g_object_ref (buffer));
570
571 IDE_EXIT;
572 }
573
574 /**
575 * ide_buffer_manager_load_file_async:
576 * @self: an #IdeBufferManager
577 * @file: (nullable): a #GFile
578 * @flags: optional flags for loading the buffer
579 * @notif: (nullable): a location for an #IdeNotification, or %NULL
580 * @cancellable: (nullable): a #GCancellable or %NULL
581 * @callback: a callback to execute upon completion of the operation
582 * @user_data: closure data for @callback
583 *
584 * Requests that @file be loaded by the buffer manager. Depending on @flags,
585 * this may result in a new view being displayed in a Builder workspace.
586 *
587 * If @file is %NULL, then a new temporary file is created with an
588 * incrementing number to denote the document, such as "unsaved file 1".
589 *
590 * After completion, @callback will be executed and you can receive the buffer
591 * that was loaded with ide_buffer_manager_load_file_finish().
592 *
593 * If a buffer has already been loaded from @file, the operation will complete
594 * using that existing buffer.
595 *
596 * If a buffer is currently loading for @file, the operation will complete
597 * using that existing buffer after it has completed loading.
598 *
599 * If @notif is non-NULL, it will be updated with status information while
600 * loading the document.
601 *
602 * Since: 3.32
603 */
604 void
ide_buffer_manager_load_file_async(IdeBufferManager * self,GFile * file,IdeBufferOpenFlags flags,IdeNotification * notif,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)605 ide_buffer_manager_load_file_async (IdeBufferManager *self,
606 GFile *file,
607 IdeBufferOpenFlags flags,
608 IdeNotification *notif,
609 GCancellable *cancellable,
610 GAsyncReadyCallback callback,
611 gpointer user_data)
612 {
613 g_autoptr(IdeBuffer) buffer = NULL;
614 g_autoptr(IdeTask) task = NULL;
615 g_autoptr(GFile) temp_file = NULL;
616 IdeBuffer *existing;
617 gboolean create_new_view = FALSE;
618 gboolean is_new = FALSE;
619
620 IDE_ENTRY;
621
622 g_return_if_fail (IDE_IS_MAIN_THREAD ());
623 g_return_if_fail (IDE_IS_BUFFER_MANAGER (self));
624 g_return_if_fail (!file || G_IS_FILE (file));
625 g_return_if_fail (!notif || IDE_IS_NOTIFICATION (notif));
626 g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
627
628 if (file == NULL)
629 file = temp_file = ide_buffer_manager_next_temp_file (self);
630
631 task = ide_task_new (self, cancellable, callback, user_data);
632 ide_task_set_source_tag (task, ide_buffer_manager_load_file_async);
633 ide_task_set_task_data (task, g_file_dup (file), g_object_unref);
634
635 /* If the file requested has already been opened, then we will return
636 * that (unless a forced reload was requested).
637 */
638 if ((existing = ide_buffer_manager_find_buffer (self, file)))
639 {
640 IdeTask *existing_task;
641
642 /* If the buffer does not need to be reloaded, just return the
643 * buffer to the user now.
644 */
645 if (!(flags & IDE_BUFFER_OPEN_FLAGS_FORCE_RELOAD))
646 {
647 ide_task_return_object (task, g_object_ref (existing));
648 IDE_EXIT;
649 }
650
651 /* If the buffer is still loading, we can just chain onto that
652 * loading operation and complete this task when that task finishes.
653 */
654 if ((existing_task = g_hash_table_lookup (self->loading_tasks, file)))
655 {
656 ide_task_chain (existing_task, task);
657 IDE_EXIT;
658 }
659
660 buffer = g_object_ref (existing);
661 }
662 else
663 {
664 /* Create the buffer and track it so we can find it later */
665 buffer = ide_buffer_manager_create_buffer (self, file,
666 (flags & IDE_BUFFER_OPEN_FLAGS_DISABLE_ADDINS) == 0,
667 temp_file != NULL);
668 is_new = TRUE;
669 }
670
671 /* Save this task for later in case we get in a second request to open
672 * the file while we are already opening it.
673 */
674 g_hash_table_insert (self->loading_tasks, g_file_dup (file), g_object_ref (task));
675
676 /* We might have listeners tracking new buffers. Apply some rules to
677 * determine if we need the UI to create a new view for the buffer.
678 */
679 g_assert (buffer != NULL);
680 g_assert (IDE_IS_BUFFER (buffer));
681 create_new_view = !(flags & IDE_BUFFER_OPEN_FLAGS_NO_VIEW) &&
682 (is_new || (flags & IDE_BUFFER_OPEN_FLAGS_BACKGROUND) == 0);
683 g_signal_emit (self, signals [LOAD_BUFFER], 0, buffer, create_new_view);
684
685 /* Now we can load the buffer asynchronously */
686 _ide_buffer_load_file_async (buffer,
687 notif,
688 cancellable,
689 ide_buffer_manager_load_file_cb,
690 g_steal_pointer (&task));
691
692 IDE_EXIT;
693 }
694
695 /**
696 * ide_buffer_manager_load_file_finish:
697 * @self: an #IdeBufferManager
698 * @result: a #GAsyncResult provided to callback
699 * @error: a location for a #GError, or %NULL
700 *
701 * Completes an asynchronous request to ide_buffer_manager_laod_file_async().
702 *
703 * Returns: (transfer full): an #IdeBuffer
704 *
705 * Since: 3.32
706 */
707 IdeBuffer *
ide_buffer_manager_load_file_finish(IdeBufferManager * self,GAsyncResult * result,GError ** error)708 ide_buffer_manager_load_file_finish (IdeBufferManager *self,
709 GAsyncResult *result,
710 GError **error)
711 {
712 IdeBuffer *ret;
713
714 IDE_ENTRY;
715
716 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
717 g_return_val_if_fail (IDE_IS_BUFFER_MANAGER (self), NULL);
718 g_return_val_if_fail (IDE_IS_TASK (result), NULL);
719
720 ret = ide_task_propagate_object (IDE_TASK (result), error);
721
722 IDE_RETURN (ret);
723 }
724
725 static void
ide_buffer_manager_save_all_cb(GObject * object,GAsyncResult * result,gpointer user_data)726 ide_buffer_manager_save_all_cb (GObject *object,
727 GAsyncResult *result,
728 gpointer user_data)
729 {
730 IdeBuffer *buffer = (IdeBuffer *)object;
731 g_autoptr(IdeTask) task = user_data;
732 g_autoptr(GError) error = NULL;
733 SaveAll *state;
734
735 IDE_ENTRY;
736
737 g_assert (IDE_IS_MAIN_THREAD ());
738 g_assert (IDE_IS_BUFFER (buffer));
739 g_assert (G_IS_ASYNC_RESULT (result));
740 g_assert (IDE_IS_TASK (task));
741
742 state = ide_task_get_task_data (task);
743
744 g_assert (state != NULL);
745 g_assert (state->buffers != NULL);
746 g_assert (state->n_active > 0);
747
748 if (!ide_buffer_save_file_finish (buffer, result, &error))
749 {
750 g_warning ("Failed to save buffer “%s”: %s",
751 ide_buffer_dup_title (buffer),
752 error->message);
753 state->had_failure = TRUE;
754 }
755
756 state->n_active--;
757
758 if (state->n_active == 0)
759 {
760 if (state->had_failure)
761 ide_task_return_new_error (task,
762 G_IO_ERROR,
763 G_IO_ERROR_FAILED,
764 "One or more buffers failed to save");
765 else
766 ide_task_return_boolean (task, TRUE);
767 }
768
769 IDE_EXIT;
770 }
771
772 static void
ide_buffer_manager_save_all_foreach_cb(IdeObject * object,IdeTask * task)773 ide_buffer_manager_save_all_foreach_cb (IdeObject *object,
774 IdeTask *task)
775 {
776 g_autoptr(IdeBuffer) buffer = NULL;
777 SaveAll *state;
778
779 g_assert (IDE_IS_MAIN_THREAD ());
780 g_assert (IDE_IS_OBJECT_BOX (object));
781 g_assert (IDE_IS_TASK (task));
782
783 buffer = ide_object_box_ref_object (IDE_OBJECT_BOX (object));
784 state = ide_task_get_task_data (task);
785
786 g_assert (IDE_IS_BUFFER (buffer));
787 g_assert (state != NULL);
788 g_assert (state->buffers != NULL);
789
790 /* Skip buffers that are loading or saving, as they are already in
791 * the correct form on disk (or will be soon). We somewhat risk beating
792 * an existing save, but that is probably okay to the user since they've
793 * already submitted the save request.
794 */
795 if (ide_buffer_get_state (buffer) != IDE_BUFFER_STATE_READY)
796 return;
797
798 /* If the file is externally modified on disk, don't save it either
799 * so we don't risk overwriting changed files. The user needs to
800 * explicitly overwrite those to avoid loosing work saved outside
801 * of Builder.
802 */
803 if (ide_buffer_get_changed_on_volume (buffer))
804 return;
805
806 g_ptr_array_add (state->buffers, g_object_ref (buffer));
807
808 state->n_active++;
809
810 ide_buffer_save_file_async (buffer,
811 NULL,
812 ide_task_get_cancellable (task),
813 NULL,
814 ide_buffer_manager_save_all_cb,
815 g_object_ref (task));
816 }
817
818 /**
819 * ide_buffer_manager_save_all_async:
820 * @self: an #IdeBufferManager
821 * @cancellable: (nullable): a #GCancellable
822 * @callback: a callback to execute upon completion
823 * @user_data: closure data for @callback
824 *
825 * Asynchronously requests that the #IdeBufferManager save all of the loaded
826 * buffers to disk.
827 *
828 * @callback will be executed after all the buffers have been saved.
829 *
830 * Since: 3.32
831 */
832 void
ide_buffer_manager_save_all_async(IdeBufferManager * self,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)833 ide_buffer_manager_save_all_async (IdeBufferManager *self,
834 GCancellable *cancellable,
835 GAsyncReadyCallback callback,
836 gpointer user_data)
837 {
838 g_autoptr(IdeTask) task = NULL;
839 SaveAll *state;
840
841 IDE_ENTRY;
842
843 g_return_if_fail (IDE_IS_MAIN_THREAD ());
844 g_return_if_fail (IDE_IS_BUFFER_MANAGER (self));
845 g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
846
847 task = ide_task_new (self, cancellable, callback, user_data);
848 ide_task_set_source_tag (task, ide_buffer_manager_save_all_async);
849
850 state = g_slice_new0 (SaveAll);
851 state->buffers = g_ptr_array_new_full (0, g_object_unref);
852 ide_task_set_task_data (task, state, save_all_free);
853
854 ide_object_foreach (IDE_OBJECT (self),
855 (GFunc)ide_buffer_manager_save_all_foreach_cb,
856 task);
857
858 if (state->n_active == 0)
859 ide_task_return_boolean (task, TRUE);
860
861 IDE_EXIT;
862 }
863
864 /**
865 * ide_buffer_manager_save_all_finish:
866 * @self: an #IdeBufferManager
867 * @result: a #GAsyncResult
868 * @error: a location for a #GError, or %NUL
869 *
870 * Completes an asynchronous request to save all buffers.
871 *
872 * Returns: %TRUE if all the buffers were saved successfully
873 *
874 * Since: 3.32
875 */
876 gboolean
ide_buffer_manager_save_all_finish(IdeBufferManager * self,GAsyncResult * result,GError ** error)877 ide_buffer_manager_save_all_finish (IdeBufferManager *self,
878 GAsyncResult *result,
879 GError **error)
880 {
881 gboolean ret;
882
883 IDE_ENTRY;
884
885 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
886 g_return_val_if_fail (IDE_IS_BUFFER_MANAGER (self), FALSE);
887 g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
888
889 ret = ide_task_propagate_boolean (IDE_TASK (result), error);
890
891 IDE_RETURN (ret);
892 }
893
894 static void
ide_buffer_manager_apply_edits_save_cb(GObject * object,GAsyncResult * result,gpointer user_data)895 ide_buffer_manager_apply_edits_save_cb (GObject *object,
896 GAsyncResult *result,
897 gpointer user_data)
898 {
899 IdeBufferManager *self = (IdeBufferManager *)object;
900 g_autoptr(IdeTask) task = user_data;
901 g_autoptr(GError) error = NULL;
902
903 IDE_ENTRY;
904
905 g_assert (IDE_IS_MAIN_THREAD ());
906 g_assert (IDE_IS_BUFFER_MANAGER (self));
907 g_assert (G_IS_ASYNC_RESULT (result));
908 g_assert (IDE_IS_TASK (task));
909
910 if (!ide_buffer_manager_save_all_finish (self, result, &error))
911 ide_task_return_error (task, g_steal_pointer (&error));
912 else
913 ide_task_return_boolean (task, TRUE);
914
915 IDE_EXIT;
916 }
917
918 static void
ide_buffer_manager_do_apply_edits(IdeBufferManager * self,GHashTable * buffers,GPtrArray * edits)919 ide_buffer_manager_do_apply_edits (IdeBufferManager *self,
920 GHashTable *buffers,
921 GPtrArray *edits)
922 {
923 IDE_ENTRY;
924
925 g_assert (IDE_IS_MAIN_THREAD ());
926 g_assert (IDE_IS_BUFFER_MANAGER (self));
927 g_assert (buffers != NULL);
928 g_assert (edits != NULL);
929
930 /* Allow each project edit to stage its GtkTextMarks */
931 for (guint i = 0; i < edits->len; i++)
932 {
933 IdeTextEdit *edit = g_ptr_array_index (edits, i);
934 IdeLocation *location;
935 IdeRange *range;
936 IdeBuffer *buffer;
937 GFile *file;
938
939 if (NULL == (range = ide_text_edit_get_range (edit)) ||
940 NULL == (location = ide_range_get_begin (range)) ||
941 NULL == (file = ide_location_get_file (location)) ||
942 NULL == (buffer = g_hash_table_lookup (buffers, file)))
943 {
944 g_warning ("Implausible failure to access buffer");
945 continue;
946 }
947
948 gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer));
949
950 _ide_text_edit_prepare (edit, buffer);
951 }
952
953 /* Now actually perform the replacement between the text marks */
954 for (guint i = 0; i < edits->len; i++)
955 {
956 IdeTextEdit *edit = g_ptr_array_index (edits, i);
957 IdeLocation *location;
958 IdeRange *range;
959 IdeBuffer *buffer;
960 GFile *file;
961
962 if (NULL == (range = ide_text_edit_get_range (edit)) ||
963 NULL == (location = ide_range_get_begin (range)) ||
964 NULL == (file = ide_location_get_file (location)) ||
965 NULL == (buffer = g_hash_table_lookup (buffers, file)))
966 {
967 g_warning ("Implausible failure to access buffer");
968 continue;
969 }
970
971 _ide_text_edit_apply (edit, buffer);
972 }
973
974 /* Complete all of our undo groups */
975 for (guint i = 0; i < edits->len; i++)
976 {
977 IdeTextEdit *edit = g_ptr_array_index (edits, i);
978 IdeLocation *location;
979 IdeRange *range;
980 IdeBuffer *buffer;
981 GFile *file;
982
983 if (NULL == (range = ide_text_edit_get_range (edit)) ||
984 NULL == (location = ide_range_get_begin (range)) ||
985 NULL == (file = ide_location_get_file (location)) ||
986 NULL == (buffer = g_hash_table_lookup (buffers, file)))
987 {
988 g_warning ("Implausible failure to access buffer");
989 continue;
990 }
991
992 gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer));
993 }
994
995 IDE_EXIT;
996 }
997
998 static void
ide_buffer_manager_apply_edits_buffer_loaded_cb(GObject * object,GAsyncResult * result,gpointer user_data)999 ide_buffer_manager_apply_edits_buffer_loaded_cb (GObject *object,
1000 GAsyncResult *result,
1001 gpointer user_data)
1002 {
1003 IdeBufferManager *self = (IdeBufferManager *)object;
1004 g_autoptr(IdeTask) task = user_data;
1005 g_autoptr(GError) error = NULL;
1006 g_autoptr(IdeBuffer) buffer = NULL;
1007 GCancellable *cancellable;
1008 EditState *state;
1009 GFile *file;
1010
1011 IDE_ENTRY;
1012
1013 g_assert (IDE_IS_MAIN_THREAD ());
1014 g_assert (IDE_IS_BUFFER_MANAGER (self));
1015 g_assert (G_IS_ASYNC_RESULT (result));
1016 g_assert (IDE_IS_TASK (task));
1017
1018 cancellable = ide_task_get_cancellable (task);
1019 state = ide_task_get_task_data (task);
1020
1021 g_assert (state != NULL);
1022 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
1023
1024 state->n_active--;
1025
1026 /* Get our buffer, if we failed, we won't proceed with edits */
1027 if (!(buffer = ide_buffer_manager_load_file_finish (self, result, &error)))
1028 {
1029 if (state->failed == FALSE)
1030 {
1031 state->failed = TRUE;
1032 ide_task_return_error (task, g_steal_pointer (&error));
1033 }
1034 }
1035
1036 /* Nothing to do if we already failed */
1037 if (state->failed)
1038 IDE_EXIT;
1039
1040 /* Save the buffer for future use when applying edits */
1041 file = ide_buffer_get_file (buffer);
1042 g_hash_table_insert (state->buffers, g_object_ref (file), g_object_ref (buffer));
1043 g_hash_table_insert (state->to_close, g_object_ref (file), g_object_ref (buffer));
1044
1045 /* If this is the last buffer to load, then we can go apply the edits. */
1046 if (state->n_active == 0)
1047 {
1048 ide_buffer_manager_do_apply_edits (self,
1049 state->buffers,
1050 state->edits);
1051 ide_buffer_manager_save_all_async (self,
1052 cancellable,
1053 ide_buffer_manager_apply_edits_save_cb,
1054 g_steal_pointer (&task));
1055 }
1056
1057 IDE_EXIT;
1058 }
1059
1060 static void
ide_buffer_manager_apply_edits_completed_cb(IdeBufferManager * self,GParamSpec * pspec,IdeTask * task)1061 ide_buffer_manager_apply_edits_completed_cb (IdeBufferManager *self,
1062 GParamSpec *pspec,
1063 IdeTask *task)
1064 {
1065 GHashTableIter iter;
1066 IdeBuffer *buffer;
1067 EditState *state;
1068
1069 IDE_ENTRY;
1070
1071 g_assert (IDE_IS_BUFFER_MANAGER (self));
1072 g_assert (pspec != NULL);
1073 g_assert (IDE_IS_TASK (task));
1074
1075 state = ide_task_get_task_data (task);
1076 g_assert (state != NULL);
1077 g_assert (state->to_close != NULL);
1078 g_assert (state->buffers != NULL);
1079
1080 g_hash_table_iter_init (&iter, state->to_close);
1081
1082 while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&buffer))
1083 {
1084 if (buffer != NULL)
1085 {
1086 IdeObjectBox *box = ide_object_box_from_object (G_OBJECT (buffer));
1087
1088 g_assert (!box || IDE_IS_OBJECT_BOX (box));
1089
1090 if (box != NULL)
1091 ide_object_destroy (IDE_OBJECT (box));
1092 }
1093 }
1094
1095 IDE_EXIT;
1096 }
1097
1098 /**
1099 * ide_buffer_manager_apply_edits_async:
1100 * @self: An #IdeBufferManager
1101 * @edits: (transfer full) (element-type IdeTextEdit):
1102 * An #GPtrArray of #IdeTextEdit.
1103 * @cancellable: (allow-none): a #GCancellable or %NULL
1104 * @callback: the callback to complete the request
1105 * @user_data: user data for @callback
1106 *
1107 * Asynchronously requests that all of @edits are applied to the buffers
1108 * in the project. If the buffer has not been loaded for a particular edit,
1109 * it will be loaded.
1110 *
1111 * @callback should call ide_buffer_manager_apply_edits_finish() to get the
1112 * result of this operation.
1113 *
1114 * Since: 3.32
1115 */
1116 void
ide_buffer_manager_apply_edits_async(IdeBufferManager * self,GPtrArray * edits,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1117 ide_buffer_manager_apply_edits_async (IdeBufferManager *self,
1118 GPtrArray *edits,
1119 GCancellable *cancellable,
1120 GAsyncReadyCallback callback,
1121 gpointer user_data)
1122 {
1123 g_autoptr(IdeTask) task = NULL;
1124 EditState *state;
1125
1126 IDE_ENTRY;
1127
1128 g_return_if_fail (IDE_IS_MAIN_THREAD ());
1129 g_return_if_fail (IDE_IS_BUFFER_MANAGER (self));
1130 g_return_if_fail (edits != NULL);
1131 g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
1132
1133 IDE_PTR_ARRAY_SET_FREE_FUNC (edits, g_object_unref);
1134
1135 task = ide_task_new (self, cancellable, callback, user_data);
1136 ide_task_set_source_tag (task, ide_buffer_manager_apply_edits_async);
1137
1138 state = g_slice_new0 (EditState);
1139 state->buffers = g_hash_table_new_full (g_file_hash,
1140 (GEqualFunc)g_file_equal,
1141 g_object_unref,
1142 _g_object_unref0);
1143 state->to_close = g_hash_table_new_full (g_file_hash,
1144 (GEqualFunc)g_file_equal,
1145 g_object_unref,
1146 _g_object_unref0);
1147 state->edits = g_steal_pointer (&edits);
1148 ide_task_set_task_data (task, state, edit_state_free);
1149
1150 g_signal_connect_object (task,
1151 "notify::completed",
1152 G_CALLBACK (ide_buffer_manager_apply_edits_completed_cb),
1153 self,
1154 G_CONNECT_SWAPPED);
1155
1156 for (guint i = 0; i < state->edits->len; i++)
1157 {
1158 IdeTextEdit *edit = g_ptr_array_index (state->edits, i);
1159 IdeLocation *location;
1160 IdeBuffer *buffer;
1161 IdeRange *range;
1162 GFile *file;
1163
1164 if (NULL == (range = ide_text_edit_get_range (edit)) ||
1165 NULL == (location = ide_range_get_begin (range)) ||
1166 NULL == (file = ide_location_get_file (location)))
1167 continue;
1168
1169 if (g_hash_table_contains (state->buffers, file))
1170 continue;
1171
1172 if ((buffer = ide_buffer_manager_find_buffer (self, file)))
1173 {
1174 g_hash_table_insert (state->buffers, g_object_ref (file), g_object_ref (buffer));
1175 continue;
1176 }
1177
1178 g_hash_table_insert (state->buffers, g_object_ref (file), NULL);
1179
1180 state->n_active++;
1181
1182 /* Load buffers, but don't create views for them since we don't want to
1183 * create lots of views if there are lots of files to edit.
1184 */
1185 ide_buffer_manager_load_file_async (self,
1186 file,
1187 IDE_BUFFER_OPEN_FLAGS_NO_VIEW |
1188 /* We are only temporary loading the buffer to replace text
1189 * there, so let's avoid any heavy useless work that the
1190 * buffer addins might do.
1191 */
1192 IDE_BUFFER_OPEN_FLAGS_DISABLE_ADDINS,
1193 NULL,
1194 cancellable,
1195 ide_buffer_manager_apply_edits_buffer_loaded_cb,
1196 g_object_ref (task));
1197 }
1198
1199 IDE_TRACE_MSG ("Waiting for %d buffers to load", state->n_active);
1200
1201 if (state->n_active == 0)
1202 {
1203 ide_buffer_manager_do_apply_edits (self, state->buffers, state->edits);
1204 ide_buffer_manager_save_all_async (self,
1205 cancellable,
1206 ide_buffer_manager_apply_edits_save_cb,
1207 g_steal_pointer (&task));
1208 }
1209
1210 IDE_EXIT;
1211 }
1212
1213 gboolean
ide_buffer_manager_apply_edits_finish(IdeBufferManager * self,GAsyncResult * result,GError ** error)1214 ide_buffer_manager_apply_edits_finish (IdeBufferManager *self,
1215 GAsyncResult *result,
1216 GError **error)
1217 {
1218 gboolean ret;
1219
1220 IDE_ENTRY;
1221
1222 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
1223 g_return_val_if_fail (IDE_IS_BUFFER_MANAGER (self), FALSE);
1224 g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
1225
1226 ret = ide_task_propagate_boolean (IDE_TASK (result), error);
1227
1228 IDE_RETURN (ret);
1229 }
1230
1231 void
_ide_buffer_manager_buffer_loaded(IdeBufferManager * self,IdeBuffer * buffer)1232 _ide_buffer_manager_buffer_loaded (IdeBufferManager *self,
1233 IdeBuffer *buffer)
1234 {
1235 g_return_if_fail (IDE_IS_MAIN_THREAD ());
1236 g_return_if_fail (IDE_IS_BUFFER_MANAGER (self));
1237 g_return_if_fail (IDE_IS_BUFFER (buffer));
1238
1239 g_signal_emit (self, signals [BUFFER_LOADED], 0, buffer);
1240 }
1241
1242 void
_ide_buffer_manager_buffer_saved(IdeBufferManager * self,IdeBuffer * buffer)1243 _ide_buffer_manager_buffer_saved (IdeBufferManager *self,
1244 IdeBuffer *buffer)
1245 {
1246 g_return_if_fail (IDE_IS_MAIN_THREAD ());
1247 g_return_if_fail (IDE_IS_BUFFER_MANAGER (self));
1248 g_return_if_fail (IDE_IS_BUFFER (buffer));
1249
1250 g_signal_emit (self, signals [BUFFER_SAVED], 0, buffer);
1251 }
1252
1253 static GType
ide_buffer_manager_get_item_type(GListModel * model)1254 ide_buffer_manager_get_item_type (GListModel *model)
1255 {
1256 g_assert (IDE_IS_MAIN_THREAD ());
1257 g_assert (IDE_IS_BUFFER_MANAGER (model));
1258
1259 return IDE_TYPE_BUFFER;
1260 }
1261
1262 static gpointer
ide_buffer_manager_get_item(GListModel * model,guint position)1263 ide_buffer_manager_get_item (GListModel *model,
1264 guint position)
1265 {
1266 IdeBufferManager *self = (IdeBufferManager *)model;
1267 g_autoptr(IdeObject) box = NULL;
1268
1269 g_assert (IDE_IS_MAIN_THREAD ());
1270 g_assert (IDE_IS_BUFFER_MANAGER (self));
1271
1272 if ((box = ide_object_get_nth_child (IDE_OBJECT (self), position)))
1273 return ide_object_box_ref_object (IDE_OBJECT_BOX (box));
1274
1275 return NULL;
1276 }
1277
1278 static guint
ide_buffer_manager_get_n_items(GListModel * model)1279 ide_buffer_manager_get_n_items (GListModel *model)
1280 {
1281 g_assert (IDE_IS_MAIN_THREAD ());
1282 g_assert (IDE_IS_BUFFER_MANAGER (model));
1283
1284 return ide_object_get_n_children (IDE_OBJECT (model));
1285 }
1286
1287 static void
list_model_iface_init(GListModelInterface * iface)1288 list_model_iface_init (GListModelInterface *iface)
1289 {
1290 iface->get_item_type = ide_buffer_manager_get_item_type;
1291 iface->get_item = ide_buffer_manager_get_item;
1292 iface->get_n_items = ide_buffer_manager_get_n_items;
1293 }
1294
1295 static void
ide_buffer_manager_foreach_cb(IdeObject * object,gpointer user_data)1296 ide_buffer_manager_foreach_cb (IdeObject *object,
1297 gpointer user_data)
1298 {
1299 const Foreach *state = user_data;
1300
1301 g_assert (IDE_IS_OBJECT (object));
1302
1303 if (IDE_IS_OBJECT_BOX (object))
1304 {
1305 g_autoptr(IdeObject) wrapped = NULL;
1306
1307 wrapped = ide_object_box_ref_object (IDE_OBJECT_BOX (object));
1308
1309 if (IDE_IS_BUFFER (wrapped))
1310 state->func (IDE_BUFFER (wrapped), state->user_data);
1311 }
1312 }
1313
1314 /**
1315 * ide_buffer_manager_foreach:
1316 * @self: a #IdeBufferManager
1317 * @foreach_func: (scope call): an #IdeBufferForeachFunc
1318 * @user_data: closure data for @foreach_func
1319 *
1320 * Calls @foreach_func for every buffer registered.
1321 *
1322 * Since: 3.32
1323 */
1324 void
ide_buffer_manager_foreach(IdeBufferManager * self,IdeBufferForeachFunc foreach_func,gpointer user_data)1325 ide_buffer_manager_foreach (IdeBufferManager *self,
1326 IdeBufferForeachFunc foreach_func,
1327 gpointer user_data)
1328 {
1329 Foreach state = { foreach_func, user_data };
1330
1331 g_return_if_fail (IDE_IS_BUFFER_MANAGER (self));
1332 g_return_if_fail (foreach_func != NULL);
1333
1334 ide_object_foreach (IDE_OBJECT (self),
1335 (GFunc)ide_buffer_manager_foreach_cb,
1336 &state);
1337 }
1338
1339 static void
ide_buffer_manager_reload_all_load_cb(GObject * object,GAsyncResult * result,gpointer user_data)1340 ide_buffer_manager_reload_all_load_cb (GObject *object,
1341 GAsyncResult *result,
1342 gpointer user_data)
1343 {
1344 IdeBufferManager *self = (IdeBufferManager *)object;
1345 g_autoptr(IdeTask) task = user_data;
1346 g_autoptr(GError) error = NULL;
1347 guint *n_active;
1348
1349 g_assert (IDE_IS_BUFFER_MANAGER (self));
1350 g_assert (G_IS_ASYNC_RESULT (result));
1351 g_assert (IDE_IS_TASK (task));
1352
1353 n_active = ide_task_get_task_data (task);
1354
1355 if (!ide_buffer_manager_load_file_finish (self, result, &error))
1356 g_warning ("Failed to reload buffer: %s", error->message);
1357
1358 (*n_active)--;
1359
1360 if (*n_active == 0)
1361 ide_task_return_boolean (task, TRUE);
1362 }
1363
1364 static void
ide_buffer_manager_reload_all_foreach_cb(IdeBuffer * buffer,IdeTask * task)1365 ide_buffer_manager_reload_all_foreach_cb (IdeBuffer *buffer,
1366 IdeTask *task)
1367 {
1368 IdeBufferManager *self;
1369 guint *n_active;
1370
1371 g_assert (IDE_IS_BUFFER (buffer));
1372 g_assert (IDE_IS_TASK (task));
1373
1374 self = ide_task_get_source_object (task);
1375 n_active = ide_task_get_task_data (task);
1376
1377 if (ide_buffer_get_changed_on_volume (buffer))
1378 {
1379 (*n_active)++;
1380
1381 ide_buffer_manager_load_file_async (self,
1382 ide_buffer_get_file (buffer),
1383 IDE_BUFFER_OPEN_FLAGS_FORCE_RELOAD,
1384 NULL,
1385 ide_task_get_cancellable (task),
1386 ide_buffer_manager_reload_all_load_cb,
1387 g_object_ref (task));
1388 }
1389 }
1390
1391 void
ide_buffer_manager_reload_all_async(IdeBufferManager * self,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1392 ide_buffer_manager_reload_all_async (IdeBufferManager *self,
1393 GCancellable *cancellable,
1394 GAsyncReadyCallback callback,
1395 gpointer user_data)
1396 {
1397 g_autoptr(IdeTask) task = NULL;
1398 guint *n_active;
1399
1400 g_return_if_fail (IDE_IS_BUFFER_MANAGER (self));
1401 g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
1402
1403 n_active = g_new0 (guint, 1);
1404
1405 task = ide_task_new (self, cancellable, callback, user_data);
1406 ide_task_set_source_tag (task, ide_buffer_manager_reload_all_async);
1407 ide_task_set_task_data (task, n_active, g_free);
1408
1409 ide_buffer_manager_foreach (self,
1410 (IdeBufferForeachFunc)ide_buffer_manager_reload_all_foreach_cb,
1411 task);
1412
1413 if (*n_active == 0)
1414 ide_task_return_boolean (task, TRUE);
1415 }
1416
1417 gboolean
ide_buffer_manager_reload_all_finish(IdeBufferManager * self,GAsyncResult * result,GError ** error)1418 ide_buffer_manager_reload_all_finish (IdeBufferManager *self,
1419 GAsyncResult *result,
1420 GError **error)
1421 {
1422 g_return_val_if_fail (IDE_IS_BUFFER_MANAGER (self), FALSE);
1423 g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
1424
1425 return ide_task_propagate_boolean (IDE_TASK (result), error);
1426 }
1427