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