1 /* ide-workbench.c
2 *
3 * Copyright 2014-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-workbench"
22
23 #include "config.h"
24
25 #include <glib/gi18n.h>
26 #include <libide-debugger.h>
27 #include <libide-threading.h>
28 #include <libpeas/peas.h>
29
30 #include "ide-build-private.h"
31 #include "ide-context-private.h"
32 #include "ide-foundry-init.h"
33 #include "ide-thread-private.h"
34 #include "ide-transfer-manager-private.h"
35
36 #include "ide-application.h"
37 #include "ide-command-manager.h"
38 #include "ide-gui-global.h"
39 #include "ide-gui-private.h"
40 #include "ide-primary-workspace.h"
41 #include "ide-workbench.h"
42 #include "ide-workbench-addin.h"
43 #include "ide-workspace.h"
44
45 /**
46 * SECTION:ide-workbench
47 * @title: IdeWorkbench
48 * @short_description: window group for all windows within a project
49 *
50 * The #IdeWorkbench is a #GtkWindowGroup containing the #IdeContext (root
51 * data-structure for a project) and all of the windows associated with the
52 * project.
53 *
54 * Usually, windows within the #IdeWorkbench are an #IdeWorkspace. They can
55 * react to changes in the #IdeContext or its descendants to represent the
56 * project and it's state.
57 *
58 * Since: 3.32
59 */
60
61 struct _IdeWorkbench
62 {
63 GtkWindowGroup parent_instance;
64
65 /* MRU of workspaces, link embedded in workspace */
66 GQueue mru_queue;
67
68 /* Owned references */
69 PeasExtensionSet *addins;
70 GCancellable *cancellable;
71 IdeContext *context;
72 IdeBuildSystem *build_system;
73 IdeProjectInfo *project_info;
74 IdeVcs *vcs;
75 IdeVcsMonitor *vcs_monitor;
76 IdeSearchEngine *search_engine;
77
78 /* Various flags */
79 guint unloaded : 1;
80 };
81
82 typedef struct
83 {
84 GPtrArray *addins;
85 IdeWorkbenchAddin *preferred;
86 GFile *file;
87 gchar *hint;
88 gchar *content_type;
89 IdeBufferOpenFlags flags;
90 gint at_line;
91 gint at_line_offset;
92 } Open;
93
94 typedef struct
95 {
96 IdeProjectInfo *project_info;
97 GPtrArray *addins;
98 GType workspace_type;
99 gint64 present_time;
100 } LoadProject;
101
102 typedef struct
103 {
104 GPtrArray *roots;
105 gchar *path;
106 } ResolveFile;
107
108 enum {
109 PROP_0,
110 PROP_CONTEXT,
111 PROP_VCS,
112 N_PROPS
113 };
114
115 static void ide_workbench_action_close (IdeWorkbench *self,
116 GVariant *param);
117 static void ide_workbench_action_open (IdeWorkbench *self,
118 GVariant *param);
119 static void ide_workbench_action_dump_tasks (IdeWorkbench *self,
120 GVariant *param);
121 static void ide_workbench_action_object_tree (IdeWorkbench *self,
122 GVariant *param);
123 static void ide_workbench_action_inspector (IdeWorkbench *self,
124 GVariant *param);
125 static void ide_workbench_action_reload_all (IdeWorkbench *self,
126 GVariant *param);
127
128
129 DZL_DEFINE_ACTION_GROUP (IdeWorkbench, ide_workbench, {
130 { "close", ide_workbench_action_close },
131 { "open", ide_workbench_action_open },
132 { "reload-files", ide_workbench_action_reload_all },
133 { "-inspector", ide_workbench_action_inspector },
134 { "-object-tree", ide_workbench_action_object_tree },
135 { "-dump-tasks", ide_workbench_action_dump_tasks },
136 })
137
138 G_DEFINE_FINAL_TYPE_WITH_CODE (IdeWorkbench, ide_workbench, GTK_TYPE_WINDOW_GROUP,
139 G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP,
140 ide_workbench_init_action_group))
141
142 static GParamSpec *properties [N_PROPS];
143
144 static void
load_project_free(LoadProject * lp)145 load_project_free (LoadProject *lp)
146 {
147 g_clear_object (&lp->project_info);
148 g_clear_pointer (&lp->addins, g_ptr_array_unref);
149 g_slice_free (LoadProject, lp);
150 }
151
152 static void
open_free(Open * o)153 open_free (Open *o)
154 {
155 g_clear_pointer (&o->addins, g_ptr_array_unref);
156 g_clear_object (&o->preferred);
157 g_clear_object (&o->file);
158 g_clear_pointer (&o->hint, g_free);
159 g_clear_pointer (&o->content_type, g_free);
160 g_slice_free (Open, o);
161 }
162
163 static void
resolve_file_free(ResolveFile * rf)164 resolve_file_free (ResolveFile *rf)
165 {
166 g_clear_pointer (&rf->roots, g_ptr_array_unref);
167 g_clear_pointer (&rf->path, g_free);
168 g_slice_free (ResolveFile, rf);
169 }
170
171 static gboolean
ignore_error(GError * error)172 ignore_error (GError *error)
173 {
174 return g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) ||
175 g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED);
176 }
177
178 /**
179 * ide_workbench_from_context:
180 * @context: an #IdeContext
181 *
182 * Helper to get the #IdeWorkbench for a given context.
183 *
184 * Returns: (transfer none) (nullable): an #IdeWorkbench or %NULL
185 *
186 * Since: 3.40
187 */
188 IdeWorkbench *
ide_workbench_from_context(IdeContext * context)189 ide_workbench_from_context (IdeContext *context)
190 {
191 g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
192
193 return IDE_WORKBENCH (g_object_get_data (G_OBJECT (context), "WORKBENCH"));
194 }
195
196 static void
ide_workbench_set_context(IdeWorkbench * self,IdeContext * context)197 ide_workbench_set_context (IdeWorkbench *self,
198 IdeContext *context)
199 {
200 g_autoptr(IdeContext) new_context = NULL;
201 g_autoptr(IdeBufferManager) bufmgr = NULL;
202 IdeBuildSystem *build_system;
203
204 g_return_if_fail (IDE_IS_WORKBENCH (self));
205 g_return_if_fail (!context || IDE_IS_CONTEXT (context));
206
207 if (context == NULL)
208 context = new_context = ide_context_new ();
209
210 /* backpointer for the workbench */
211 g_object_set_data (G_OBJECT (context), "WORKBENCH", self);
212
213 g_set_object (&self->context, context);
214
215 /* Make sure we have access to buffer manager early */
216 bufmgr = ide_object_ensure_child_typed (IDE_OBJECT (context), IDE_TYPE_BUFFER_MANAGER);
217
218 /* And use a fallback build system if one is not already available */
219 if ((build_system = ide_context_peek_child_typed (context, IDE_TYPE_BUILD_SYSTEM)))
220 self->build_system = g_object_ref (build_system);
221 else
222 self->build_system = ide_object_ensure_child_typed (IDE_OBJECT (context), IDE_TYPE_FALLBACK_BUILD_SYSTEM);
223 }
224
225 static void
ide_workbench_addin_added_workspace_cb(IdeWorkspace * workspace,IdeWorkbenchAddin * addin)226 ide_workbench_addin_added_workspace_cb (IdeWorkspace *workspace,
227 IdeWorkbenchAddin *addin)
228 {
229 g_assert (IDE_IS_WORKSPACE (workspace));
230 g_assert (IDE_IS_WORKBENCH_ADDIN (addin));
231
232 ide_workbench_addin_workspace_added (addin, workspace);
233 }
234
235 static void
ide_workbench_addin_removed_workspace_cb(IdeWorkspace * workspace,IdeWorkbenchAddin * addin)236 ide_workbench_addin_removed_workspace_cb (IdeWorkspace *workspace,
237 IdeWorkbenchAddin *addin)
238 {
239 g_assert (IDE_IS_WORKSPACE (workspace));
240 g_assert (IDE_IS_WORKBENCH_ADDIN (addin));
241
242 ide_workbench_addin_workspace_removed (addin, workspace);
243 }
244
245 static void
ide_workbench_addin_added_cb(PeasExtensionSet * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)246 ide_workbench_addin_added_cb (PeasExtensionSet *set,
247 PeasPluginInfo *plugin_info,
248 PeasExtension *exten,
249 gpointer user_data)
250 {
251 IdeWorkbench *self = user_data;
252 IdeWorkbenchAddin *addin = (IdeWorkbenchAddin *)exten;
253
254 g_assert (PEAS_IS_EXTENSION_SET (set));
255 g_assert (plugin_info != NULL);
256 g_assert (IDE_IS_WORKBENCH_ADDIN (addin));
257 g_assert (IDE_IS_WORKBENCH (self));
258
259 ide_workbench_addin_load (addin, self);
260
261 /* Notify of the VCS system up-front */
262 if (self->vcs != NULL)
263 ide_workbench_addin_vcs_changed (addin, self->vcs);
264
265 /*
266 * If we already loaded a project, then give the plugin a
267 * chance to handle that, even if it is delayed a bit.
268 */
269
270 if (self->project_info != NULL)
271 ide_workbench_addin_load_project_async (addin, self->project_info, NULL, NULL, NULL);
272
273 ide_workbench_foreach_workspace (self,
274 (GtkCallback)ide_workbench_addin_added_workspace_cb,
275 addin);
276 }
277
278 static void
ide_workbench_addin_removed_cb(PeasExtensionSet * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)279 ide_workbench_addin_removed_cb (PeasExtensionSet *set,
280 PeasPluginInfo *plugin_info,
281 PeasExtension *exten,
282 gpointer user_data)
283 {
284 IdeWorkbench *self = user_data;
285 IdeWorkbenchAddin *addin = (IdeWorkbenchAddin *)exten;
286
287 g_assert (PEAS_IS_EXTENSION_SET (set));
288 g_assert (plugin_info != NULL);
289 g_assert (IDE_IS_WORKBENCH_ADDIN (addin));
290 g_assert (IDE_IS_WORKBENCH (self));
291
292 /* Notify of workspace removals so addins don't need to manually
293 * track them for cleanup.
294 */
295 ide_workbench_foreach_workspace (self,
296 (GtkCallback)ide_workbench_addin_removed_workspace_cb,
297 addin);
298
299 ide_workbench_addin_unload (addin, self);
300 }
301
302 static void
ide_workbench_notify_context_title(IdeWorkbench * self,GParamSpec * pspec,IdeContext * context)303 ide_workbench_notify_context_title (IdeWorkbench *self,
304 GParamSpec *pspec,
305 IdeContext *context)
306 {
307 g_autofree gchar *formatted = NULL;
308 g_autofree gchar *title = NULL;
309
310 g_assert (IDE_IS_MAIN_THREAD ());
311 g_assert (IDE_IS_WORKBENCH (self));
312 g_assert (IDE_IS_CONTEXT (context));
313
314 title = ide_context_dup_title (context);
315 formatted = g_strdup_printf (_("Builder — %s"), title);
316 ide_workbench_foreach_workspace (self,
317 (GtkCallback)gtk_window_set_title,
318 formatted);
319 }
320
321 static void
ide_workbench_notify_context_workdir(IdeWorkbench * self,GParamSpec * pspec,IdeContext * context)322 ide_workbench_notify_context_workdir (IdeWorkbench *self,
323 GParamSpec *pspec,
324 IdeContext *context)
325 {
326 g_autoptr(GFile) workdir = NULL;
327
328 g_assert (IDE_IS_MAIN_THREAD ());
329 g_assert (IDE_IS_WORKBENCH (self));
330 g_assert (IDE_IS_CONTEXT (context));
331
332 workdir = ide_context_ref_workdir (context);
333 ide_vcs_monitor_set_root (self->vcs_monitor, workdir);
334 }
335
336 static void
ide_workbench_constructed(GObject * object)337 ide_workbench_constructed (GObject *object)
338 {
339 IdeWorkbench *self = (IdeWorkbench *)object;
340
341 g_assert (IDE_IS_WORKBENCH (self));
342
343 if (self->context == NULL)
344 self->context = ide_context_new ();
345
346 g_signal_connect_object (self->context,
347 "notify::title",
348 G_CALLBACK (ide_workbench_notify_context_title),
349 self,
350 G_CONNECT_SWAPPED);
351
352 g_signal_connect_object (self->context,
353 "notify::workdir",
354 G_CALLBACK (ide_workbench_notify_context_workdir),
355 self,
356 G_CONNECT_SWAPPED);
357
358 G_OBJECT_CLASS (ide_workbench_parent_class)->constructed (object);
359
360 self->vcs_monitor = g_object_new (IDE_TYPE_VCS_MONITOR,
361 "parent", self->context,
362 NULL);
363
364 self->addins = peas_extension_set_new (peas_engine_get_default (),
365 IDE_TYPE_WORKBENCH_ADDIN,
366 NULL);
367
368 g_signal_connect (self->addins,
369 "extension-added",
370 G_CALLBACK (ide_workbench_addin_added_cb),
371 self);
372
373 g_signal_connect (self->addins,
374 "extension-removed",
375 G_CALLBACK (ide_workbench_addin_removed_cb),
376 self);
377
378 peas_extension_set_foreach (self->addins,
379 ide_workbench_addin_added_cb,
380 self);
381
382 /* Load command providers (which may register shortcuts) */
383 (void)ide_command_manager_from_context (self->context);
384 }
385
386 static void
ide_workbench_finalize(GObject * object)387 ide_workbench_finalize (GObject *object)
388 {
389 IdeWorkbench *self = (IdeWorkbench *)object;
390
391 g_assert (IDE_IS_MAIN_THREAD ());
392
393 if (self->context != NULL)
394 g_object_set_data (G_OBJECT (self->context), "WORKBENCH", NULL);
395
396 g_clear_object (&self->build_system);
397 g_clear_object (&self->vcs);
398 g_clear_object (&self->search_engine);
399 g_clear_object (&self->project_info);
400 g_clear_object (&self->cancellable);
401 g_clear_object (&self->context);
402
403 G_OBJECT_CLASS (ide_workbench_parent_class)->finalize (object);
404 }
405
406 static void
ide_workbench_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)407 ide_workbench_get_property (GObject *object,
408 guint prop_id,
409 GValue *value,
410 GParamSpec *pspec)
411 {
412 IdeWorkbench *self = IDE_WORKBENCH (object);
413
414 g_assert (IDE_IS_MAIN_THREAD ());
415
416 switch (prop_id)
417 {
418 case PROP_CONTEXT:
419 g_value_set_object (value, ide_workbench_get_context (self));
420 break;
421
422 default:
423 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
424 }
425 }
426
427 static void
ide_workbench_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)428 ide_workbench_set_property (GObject *object,
429 guint prop_id,
430 const GValue *value,
431 GParamSpec *pspec)
432 {
433 IdeWorkbench *self = IDE_WORKBENCH (object);
434
435 g_assert (IDE_IS_MAIN_THREAD ());
436
437 switch (prop_id)
438 {
439 case PROP_CONTEXT:
440 ide_workbench_set_context (self, g_value_get_object (value));
441 break;
442
443 default:
444 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
445 }
446 }
447
448 static void
ide_workbench_class_init(IdeWorkbenchClass * klass)449 ide_workbench_class_init (IdeWorkbenchClass *klass)
450 {
451 GObjectClass *object_class = G_OBJECT_CLASS (klass);
452
453 object_class->constructed = ide_workbench_constructed;
454 object_class->finalize = ide_workbench_finalize;
455 object_class->get_property = ide_workbench_get_property;
456 object_class->set_property = ide_workbench_set_property;
457
458 /**
459 * IdeWorkbench:context:
460 *
461 * The "context" property is the #IdeContext for the project.
462 *
463 * The #IdeContext is the root #IdeObject used in the tree of
464 * objects representing the project and the workings of the IDE.
465 *
466 * Since: 3.32
467 */
468 properties [PROP_CONTEXT] =
469 g_param_spec_object ("context",
470 "Context",
471 "The IdeContext for the workbench",
472 IDE_TYPE_CONTEXT,
473 (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
474
475 /**
476 * IdeWorkbench:vcs:
477 *
478 * The "vcs" property contains an #IdeVcs that represents the version control
479 * system that is currently loaded for the project.
480 *
481 * The #IdeVcs is registered by an #IdeWorkbenchAddin when loading a project.
482 *
483 * Since: 3.32
484 */
485 properties [PROP_VCS] =
486 g_param_spec_object ("vcs",
487 "Vcs",
488 "The version control system, if any",
489 IDE_TYPE_VCS,
490 (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
491
492 g_object_class_install_properties (object_class, N_PROPS, properties);
493 }
494
495 static void
ide_workbench_init(IdeWorkbench * self)496 ide_workbench_init (IdeWorkbench *self)
497 {
498 }
499
500 static void
collect_addins_cb(PeasExtensionSet * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)501 collect_addins_cb (PeasExtensionSet *set,
502 PeasPluginInfo *plugin_info,
503 PeasExtension *exten,
504 gpointer user_data)
505 {
506 GPtrArray *ar = user_data;
507 g_ptr_array_add (ar, g_object_ref (exten));
508 }
509
510 static GPtrArray *
ide_workbench_collect_addins(IdeWorkbench * self)511 ide_workbench_collect_addins (IdeWorkbench *self)
512 {
513 g_autoptr(GPtrArray) ar = NULL;
514
515 g_assert (IDE_IS_WORKBENCH (self));
516
517 ar = g_ptr_array_new_with_free_func (g_object_unref);
518 if (self->addins != NULL)
519 peas_extension_set_foreach (self->addins, collect_addins_cb, ar);
520 return g_steal_pointer (&ar);
521 }
522
523 static IdeWorkbenchAddin *
ide_workbench_find_addin(IdeWorkbench * self,const gchar * hint)524 ide_workbench_find_addin (IdeWorkbench *self,
525 const gchar *hint)
526 {
527 PeasEngine *engine;
528 PeasPluginInfo *plugin_info;
529 PeasExtension *exten = NULL;
530
531 g_return_val_if_fail (IDE_IS_WORKBENCH (self), NULL);
532 g_return_val_if_fail (hint != NULL, NULL);
533
534 engine = peas_engine_get_default ();
535
536 if ((plugin_info = peas_engine_get_plugin_info (engine, hint)))
537 exten = peas_extension_set_get_extension (self->addins, plugin_info);
538
539 return exten ? g_object_ref (IDE_WORKBENCH_ADDIN (exten)) : NULL;
540 }
541
542 /**
543 * ide_workbench_new:
544 *
545 * Creates a new #IdeWorkbench.
546 *
547 * This does not create any windows, you'll need to request that a workspace
548 * be created based on the kind of workspace you want to display to the user.
549 *
550 * Returns: an #IdeWorkbench
551 *
552 * Since: 3.32
553 */
554 IdeWorkbench *
ide_workbench_new(void)555 ide_workbench_new (void)
556 {
557 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
558
559 return g_object_new (IDE_TYPE_WORKBENCH, NULL);
560 }
561
562 /**
563 * ide_workbench_new_for_context:
564 *
565 * Creates a new #IdeWorkbench using @context for the #IdeWorkbench:context.
566 *
567 * Returns: (transfer full): an #IdeWorkbench
568 *
569 * Since: 3.32
570 */
571 IdeWorkbench *
ide_workbench_new_for_context(IdeContext * context)572 ide_workbench_new_for_context (IdeContext *context)
573 {
574 g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
575
576 return g_object_new (IDE_TYPE_CONTEXT,
577 "visible", TRUE,
578 NULL);
579 }
580
581 /**
582 * ide_workbench_get_context:
583 * @self: an #IdeWorkbench
584 *
585 * Gets the #IdeContext for the workbench.
586 *
587 * Returns: (transfer none): an #IdeContext
588 *
589 * Since: 3.32
590 */
591 IdeContext *
ide_workbench_get_context(IdeWorkbench * self)592 ide_workbench_get_context (IdeWorkbench *self)
593 {
594 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
595 g_return_val_if_fail (IDE_IS_WORKBENCH (self), NULL);
596
597 return self->context;
598 }
599
600 /**
601 * ide_workbench_from_widget:
602 * @widget: a #GtkWidget
603 *
604 * Finds the #IdeWorkbench associated with a widget.
605 *
606 * Returns: (nullable) (transfer none): an #IdeWorkbench or %NULL
607 *
608 * Since: 3.32
609 */
610 IdeWorkbench *
ide_workbench_from_widget(GtkWidget * widget)611 ide_workbench_from_widget (GtkWidget *widget)
612 {
613 GtkWindowGroup *group;
614 GtkWidget *toplevel;
615
616 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
617 g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
618
619 /*
620 * The workbench is a window group, and the workspaces belong to us. So we
621 * just need to get the toplevel window group property, and cast.
622 */
623
624 if ((toplevel = gtk_widget_get_toplevel (widget)) &&
625 GTK_IS_WINDOW (toplevel) &&
626 (group = gtk_window_get_group (GTK_WINDOW (toplevel))) &&
627 IDE_IS_WORKBENCH (group))
628 return IDE_WORKBENCH (group);
629
630 return NULL;
631 }
632
633 /**
634 * ide_workbench_foreach_workspace:
635 * @self: an #IdeWorkbench
636 * @callback: (scope call): a #GtkCallback to call for each #IdeWorkspace
637 * @user_data: user data for @callback
638 *
639 * Iterates the available workspaces in the workbench. Workspaces are iterated
640 * in most-recently-used order.
641 *
642 * Since: 3.32
643 */
644 void
ide_workbench_foreach_workspace(IdeWorkbench * self,GtkCallback callback,gpointer user_data)645 ide_workbench_foreach_workspace (IdeWorkbench *self,
646 GtkCallback callback,
647 gpointer user_data)
648 {
649 GList *copy;
650
651 g_return_if_fail (IDE_IS_MAIN_THREAD ());
652 g_return_if_fail (IDE_IS_WORKBENCH (self));
653 g_return_if_fail (callback != NULL);
654
655 /* Copy for re-entrancy safety */
656 copy = g_list_copy (self->mru_queue.head);
657
658 for (const GList *iter = copy; iter; iter = iter->next)
659 {
660 IdeWorkspace *workspace = iter->data;
661 g_assert (IDE_IS_WORKSPACE (workspace));
662 callback (GTK_WIDGET (workspace), user_data);
663 }
664
665 g_list_free (copy);
666 }
667
668 /**
669 * ide_workbench_foreach_page:
670 * @self: a #IdeWorkbench
671 * @callback: (scope call): a callback to execute for each page
672 * @user_data: closure data for @callback
673 *
674 * Calls @callback for every page loaded in the workbench, by iterating
675 * workspaces in order of most-recently-used.
676 *
677 * Since: 3.32
678 */
679 void
ide_workbench_foreach_page(IdeWorkbench * self,GtkCallback callback,gpointer user_data)680 ide_workbench_foreach_page (IdeWorkbench *self,
681 GtkCallback callback,
682 gpointer user_data)
683 {
684 GList *copy;
685
686 g_return_if_fail (IDE_IS_WORKBENCH (self));
687 g_return_if_fail (callback != NULL);
688
689 /* Make a copy to be safe against auto-cleanup removals */
690 copy = g_list_copy (self->mru_queue.head);
691 for (const GList *iter = copy; iter; iter = iter->next)
692 {
693 IdeWorkspace *workspace = iter->data;
694 g_assert (IDE_IS_WORKSPACE (workspace));
695 ide_workspace_foreach_page (workspace, callback, user_data);
696 }
697 g_list_free (copy);
698 }
699
700 static void
ide_workbench_workspace_has_toplevel_focus_cb(IdeWorkbench * self,GParamSpec * pspec,IdeWorkspace * workspace)701 ide_workbench_workspace_has_toplevel_focus_cb (IdeWorkbench *self,
702 GParamSpec *pspec,
703 IdeWorkspace *workspace)
704 {
705 g_assert (IDE_IS_WORKBENCH (self));
706 g_assert (IDE_IS_WORKSPACE (workspace));
707 g_assert (gtk_window_get_group (GTK_WINDOW (workspace)) == GTK_WINDOW_GROUP (self));
708
709 if (gtk_window_has_toplevel_focus (GTK_WINDOW (workspace)))
710 {
711 GList *mru_link = _ide_workspace_get_mru_link (workspace);
712
713 g_queue_unlink (&self->mru_queue, mru_link);
714
715 g_assert (mru_link->prev == NULL);
716 g_assert (mru_link->next == NULL);
717 g_assert (mru_link->data == (gpointer)workspace);
718
719 g_queue_push_head_link (&self->mru_queue, mru_link);
720 }
721 }
722
723 static void
insert_action_groups_foreach_cb(IdeWorkspace * workspace,gpointer user_data)724 insert_action_groups_foreach_cb (IdeWorkspace *workspace,
725 gpointer user_data)
726 {
727 IdeWorkbench *self = user_data;
728 struct {
729 const gchar *name;
730 GType child_type;
731 } groups[] = {
732 { "config-manager", IDE_TYPE_CONFIG_MANAGER },
733 { "build-manager", IDE_TYPE_BUILD_MANAGER },
734 { "device-manager", IDE_TYPE_DEVICE_MANAGER },
735 { "run-manager", IDE_TYPE_RUN_MANAGER },
736 { "test-manager", IDE_TYPE_TEST_MANAGER },
737 };
738
739 g_assert (IDE_IS_MAIN_THREAD ());
740 g_assert (IDE_IS_WORKBENCH (self));
741 g_assert (IDE_IS_WORKSPACE (workspace));
742
743 for (guint i = 0; i < G_N_ELEMENTS (groups); i++)
744 {
745 IdeObject *child;
746
747 if ((child = ide_context_peek_child_typed (self->context, groups[i].child_type)))
748 gtk_widget_insert_action_group (GTK_WIDGET (workspace),
749 groups[i].name,
750 G_ACTION_GROUP (child));
751 }
752 }
753
754 /**
755 * ide_workbench_add_workspace:
756 * @self: an #IdeWorkbench
757 * @workspace: an #IdeWorkspace
758 *
759 * Adds @workspace to @workbench.
760 *
761 * Since: 3.32
762 */
763 void
ide_workbench_add_workspace(IdeWorkbench * self,IdeWorkspace * workspace)764 ide_workbench_add_workspace (IdeWorkbench *self,
765 IdeWorkspace *workspace)
766 {
767 g_autoptr(GPtrArray) addins = NULL;
768 IdeCommandManager *command_manager;
769 GList *mru_link;
770
771 g_return_if_fail (IDE_IS_MAIN_THREAD ());
772 g_return_if_fail (IDE_IS_WORKBENCH (self));
773 g_return_if_fail (IDE_IS_WORKSPACE (workspace));
774
775 /* Now add the window to the workspace (which takes no reference, as the
776 * window will take a reference back to us.
777 */
778 if (gtk_window_get_group (GTK_WINDOW (workspace)) != GTK_WINDOW_GROUP (self))
779 gtk_window_group_add_window (GTK_WINDOW_GROUP (self), GTK_WINDOW (workspace));
780
781 g_assert (gtk_window_has_group (GTK_WINDOW (workspace)));
782 g_assert (gtk_window_get_group (GTK_WINDOW (workspace)) == GTK_WINDOW_GROUP (self));
783
784 /* Now place the workspace into our MRU tracking */
785 mru_link = _ide_workspace_get_mru_link (workspace);
786
787 /* New workspaces are expected to be displayed right away, so we can
788 * just push the window onto the head.
789 */
790 g_queue_push_head_link (&self->mru_queue, mru_link);
791
792 /* Update the context for the workspace, even if we're not loaded,
793 * this IdeContext will be updated later.
794 */
795 _ide_workspace_set_context (workspace, self->context);
796
797 /* This causes the workspace to get an additional reference to the group
798 * (which already happens from GtkWindow:group), but IdeWorkspace will
799 * remove itself in IdeWorkspace.destroy.
800 */
801 gtk_widget_insert_action_group (GTK_WIDGET (workspace),
802 "workbench",
803 G_ACTION_GROUP (self));
804
805 /* Give the workspace access to all the action groups of the context that
806 * might be useful for them to access (debug-manager, run-manager, etc).
807 */
808 if (self->project_info != NULL)
809 insert_action_groups_foreach_cb (workspace, self);
810
811 /* Track toplevel focus changes to maintain a most-recently-used queue. */
812 g_signal_connect_object (workspace,
813 "notify::has-toplevel-focus",
814 G_CALLBACK (ide_workbench_workspace_has_toplevel_focus_cb),
815 self,
816 G_CONNECT_SWAPPED);
817
818 /* Give access to transfer-manager */
819 gtk_widget_insert_action_group (GTK_WIDGET (workspace),
820 "transfer-manager",
821 _ide_transfer_manager_get_actions (NULL));
822
823 /* Notify all the addins about the new workspace. */
824 if ((addins = ide_workbench_collect_addins (self)))
825 {
826 for (guint i = 0; i < addins->len; i++)
827 {
828 IdeWorkbenchAddin *addin = g_ptr_array_index (addins, i);
829 ide_workbench_addin_workspace_added (addin, workspace);
830 }
831 }
832
833 if (!gtk_window_get_title (GTK_WINDOW (workspace)))
834 {
835 g_autofree gchar *title = NULL;
836 g_autofree gchar *formatted = NULL;
837
838 title = ide_context_dup_title (self->context);
839 formatted = g_strdup_printf (_("Builder — %s"), title);
840 gtk_window_set_title (GTK_WINDOW (workspace), formatted);
841 }
842
843 /* Load shortcuts for commands */
844 command_manager = ide_command_manager_from_context (self->context);
845 _ide_command_manager_init_shortcuts (command_manager, workspace);
846 }
847
848 /**
849 * ide_workbench_remove_workspace:
850 * @self: an #IdeWorkbench
851 * @workspace: an #IdeWorkspace
852 *
853 * Removes @workspace from @workbench.
854 *
855 * Since: 3.32
856 */
857 void
ide_workbench_remove_workspace(IdeWorkbench * self,IdeWorkspace * workspace)858 ide_workbench_remove_workspace (IdeWorkbench *self,
859 IdeWorkspace *workspace)
860 {
861 g_autoptr(GPtrArray) addins = NULL;
862 IdeCommandManager *command_manager;
863 GList *list;
864 GList *mru_link;
865 guint count = 0;
866
867 g_return_if_fail (IDE_IS_MAIN_THREAD ());
868 g_return_if_fail (IDE_IS_WORKBENCH (self));
869 g_return_if_fail (IDE_IS_WORKSPACE (workspace));
870
871 /* Stop tracking MRU changes */
872 mru_link = _ide_workspace_get_mru_link (workspace);
873 g_queue_unlink (&self->mru_queue, mru_link);
874 g_signal_handlers_disconnect_by_func (workspace,
875 G_CALLBACK (ide_workbench_workspace_has_toplevel_focus_cb),
876 self);
877
878 /* Remove any shortcuts that were registered by command providers */
879 command_manager = ide_command_manager_from_context (self->context);
880 _ide_command_manager_unload_shortcuts (command_manager, workspace);
881
882 /* Notify all the addins about losing the workspace. */
883 if ((addins = ide_workbench_collect_addins (self)))
884 {
885 for (guint i = 0; i < addins->len; i++)
886 {
887 IdeWorkbenchAddin *addin = g_ptr_array_index (addins, i);
888 ide_workbench_addin_workspace_removed (addin, workspace);
889 }
890 }
891
892 /* Clear our action group (which drops an additional back-reference) */
893 gtk_widget_insert_action_group (GTK_WIDGET (workspace), "workbench", NULL);
894
895 /* Only cleanup the group if it hasn't already been removed */
896 if (gtk_window_has_group (GTK_WINDOW (workspace)))
897 gtk_window_group_remove_window (GTK_WINDOW_GROUP (self), GTK_WINDOW (workspace));
898
899 /*
900 * If this is our last workspace being closed, then we want to
901 * try to cleanup the workbench and shut things down.
902 */
903
904 list = gtk_window_group_list_windows (GTK_WINDOW_GROUP (self));
905 for (const GList *iter = list; iter; iter = iter->next)
906 {
907 GtkWindow *window = iter->data;
908
909 if (IDE_IS_WORKSPACE (window) && workspace != IDE_WORKSPACE (window))
910 count++;
911 }
912 g_list_free (list);
913
914 /*
915 * If there are no more workspaces left, then we will want to also
916 * unload the workbench opportunistically, so that the application
917 * can exit cleanly.
918 */
919 if (count == 0 && self->unloaded == FALSE)
920 ide_workbench_unload_async (self, NULL, NULL, NULL);
921 }
922
923 /**
924 * ide_workbench_focus_workspace:
925 * @self: an #IdeWorkbench
926 * @workspace: an #IdeWorkspace
927 *
928 * Requests that @workspace be raised in the windows of @self, and
929 * displayed to the user.
930 *
931 * Since: 3.32
932 */
933 void
ide_workbench_focus_workspace(IdeWorkbench * self,IdeWorkspace * workspace)934 ide_workbench_focus_workspace (IdeWorkbench *self,
935 IdeWorkspace *workspace)
936 {
937 g_return_if_fail (IDE_IS_MAIN_THREAD ());
938 g_return_if_fail (IDE_IS_WORKBENCH (self));
939 g_return_if_fail (IDE_IS_WORKSPACE (workspace));
940
941 ide_gtk_window_present (GTK_WINDOW (workspace));
942 }
943
944 static void
ide_workbench_project_loaded_foreach_cb(PeasExtensionSet * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)945 ide_workbench_project_loaded_foreach_cb (PeasExtensionSet *set,
946 PeasPluginInfo *plugin_info,
947 PeasExtension *exten,
948 gpointer user_data)
949 {
950 IdeWorkbenchAddin *addin = (IdeWorkbenchAddin *)exten;
951 IdeWorkbench *self = user_data;
952
953 g_assert (IDE_IS_MAIN_THREAD ());
954 g_assert (PEAS_IS_EXTENSION_SET (set));
955 g_assert (IDE_IS_WORKBENCH_ADDIN (addin));
956 g_assert (IDE_IS_WORKBENCH (self));
957 g_assert (IDE_IS_PROJECT_INFO (self->project_info));
958
959 ide_workbench_addin_project_loaded (addin, self->project_info);
960 }
961
962 static void
ide_workbench_load_project_completed(IdeWorkbench * self,IdeTask * task)963 ide_workbench_load_project_completed (IdeWorkbench *self,
964 IdeTask *task)
965 {
966 IdeBuildManager *build_manager;
967 LoadProject *lp;
968
969 g_assert (IDE_IS_WORKBENCH (self));
970 g_assert (IDE_IS_TASK (task));
971
972 lp = ide_task_get_task_data (task);
973
974 g_assert (lp != NULL);
975 g_assert (lp->addins != NULL);
976 g_assert (lp->addins->len == 0);
977
978 /* If we did not get a VCS as part of the loading process, set the
979 * fallback VCS implementation.
980 */
981 if (self->vcs == NULL)
982 {
983 g_autoptr(GFile) workdir = ide_context_ref_workdir (self->context);
984 g_autoptr(IdeDirectoryVcs) vcs = ide_directory_vcs_new (workdir);
985
986 ide_workbench_set_vcs (self, IDE_VCS (vcs));
987 }
988
989 /* Create the search engine up-front */
990 if (self->search_engine == NULL)
991 self->search_engine = ide_object_ensure_child_typed (IDE_OBJECT (self->context),
992 IDE_TYPE_SEARCH_ENGINE);
993
994 if (lp->workspace_type != G_TYPE_INVALID)
995 {
996 IdeWorkspace *workspace;
997
998 workspace = g_object_new (lp->workspace_type,
999 "application", IDE_APPLICATION_DEFAULT,
1000 NULL);
1001 ide_workbench_add_workspace (self, IDE_WORKSPACE (workspace));
1002 gtk_window_present_with_time (GTK_WINDOW (workspace), lp->present_time);
1003 }
1004
1005 /* Give workspaces access to the various GActionGroups */
1006 ide_workbench_foreach_workspace (self,
1007 (GtkCallback)insert_action_groups_foreach_cb,
1008 self);
1009
1010 /* Notify addins that projects have loaded */
1011 peas_extension_set_foreach (self->addins,
1012 ide_workbench_project_loaded_foreach_cb,
1013 self);
1014
1015 /* Now that we have a workspace window for the project, we can allow
1016 * the build manager to start.
1017 */
1018 build_manager = ide_build_manager_from_context (self->context);
1019 _ide_build_manager_start (build_manager);
1020
1021 ide_task_return_boolean (task, TRUE);
1022 }
1023
1024 static void
ide_workbench_load_project_cb(GObject * object,GAsyncResult * result,gpointer user_data)1025 ide_workbench_load_project_cb (GObject *object,
1026 GAsyncResult *result,
1027 gpointer user_data)
1028 {
1029 IdeWorkbenchAddin *addin = (IdeWorkbenchAddin *)object;
1030 g_autoptr(IdeTask) task = user_data;
1031 g_autoptr(GError) error = NULL;
1032 IdeWorkbench *self;
1033 LoadProject *lp;
1034
1035 g_assert (IDE_IS_WORKBENCH_ADDIN (addin));
1036 g_assert (G_IS_ASYNC_RESULT (result));
1037 g_assert (IDE_IS_TASK (task));
1038
1039 self = ide_task_get_source_object (task);
1040 lp = ide_task_get_task_data (task);
1041
1042 g_assert (IDE_IS_WORKBENCH (self));
1043 g_assert (lp != NULL);
1044 g_assert (IDE_IS_PROJECT_INFO (lp->project_info));
1045 g_assert (lp->addins != NULL);
1046 g_assert (lp->addins->len > 0);
1047
1048 if (!ide_workbench_addin_load_project_finish (addin, result, &error))
1049 {
1050 if (!ignore_error (error))
1051 g_warning ("%s addin failed to load project: %s",
1052 G_OBJECT_TYPE_NAME (addin), error->message);
1053 }
1054
1055 g_ptr_array_remove (lp->addins, addin);
1056
1057 if (lp->addins->len == 0)
1058 ide_workbench_load_project_completed (self, task);
1059 }
1060
1061 static void
ide_workbench_init_foundry_cb(GObject * object,GAsyncResult * result,gpointer user_data)1062 ide_workbench_init_foundry_cb (GObject *object,
1063 GAsyncResult *result,
1064 gpointer user_data)
1065 {
1066 g_autoptr(IdeTask) task = user_data;
1067 g_autoptr(GError) error = NULL;
1068 IdeWorkbench *self;
1069 GCancellable *cancellable;
1070 LoadProject *lp;
1071
1072 g_assert (IDE_IS_MAIN_THREAD ());
1073 g_assert (G_IS_ASYNC_RESULT (result));
1074 g_assert (IDE_IS_TASK (task));
1075
1076 if (!_ide_foundry_init_finish (result, &error))
1077 g_critical ("Failed to initialize foundry: %s", error->message);
1078
1079 cancellable = ide_task_get_cancellable (task);
1080 self = ide_task_get_source_object (task);
1081 lp = ide_task_get_task_data (task);
1082
1083 g_assert (IDE_IS_WORKBENCH (self));
1084 g_assert (lp != NULL);
1085 g_assert (lp->addins != NULL);
1086 g_assert (IDE_IS_PROJECT_INFO (lp->project_info));
1087
1088 /* Now, we need to notify all of the workbench addins that we're
1089 * opening the project. Once they have all completed, we'll create the
1090 * new workspace window and attach it. That saves us the work of
1091 * rendering various frames of the during the intensive load process.
1092 */
1093
1094
1095 for (guint i = 0; i < lp->addins->len; i++)
1096 {
1097 IdeWorkbenchAddin *addin = g_ptr_array_index (lp->addins, i);
1098
1099 ide_workbench_addin_load_project_async (addin,
1100 lp->project_info,
1101 cancellable,
1102 ide_workbench_load_project_cb,
1103 g_object_ref (task));
1104 }
1105
1106 if (lp->addins->len == 0)
1107 ide_workbench_load_project_completed (self, task);
1108 }
1109
1110 /**
1111 * ide_workbench_load_project_async:
1112 * @self: a #IdeWorkbench
1113 * @project_info: an #IdeProjectInfo describing the project to open
1114 * @cancellable: (nullable): a #GCancellable or %NULL
1115 * @callback: (nullable): a #GAsyncReadyCallback to execute upon completion
1116 * @user_data: user data for @callback
1117 *
1118 * Requests that a project be opened in the workbench.
1119 *
1120 * @project_info should contain enough information to discover and load the
1121 * project. Depending on the various fields of the #IdeProjectInfo,
1122 * different plugins may become active as part of loading the project.
1123 *
1124 * Note that this may only be called once for an #IdeWorkbench. If you need
1125 * to open a second project, you need to create and register a second
1126 * workbench first, and then open using that secondary workbench.
1127 *
1128 * @callback should call ide_workbench_load_project_finish() to obtain the
1129 * result of the open request.
1130 *
1131 * Since: 3.32
1132 */
1133 void
ide_workbench_load_project_async(IdeWorkbench * self,IdeProjectInfo * project_info,GType workspace_type,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1134 ide_workbench_load_project_async (IdeWorkbench *self,
1135 IdeProjectInfo *project_info,
1136 GType workspace_type,
1137 GCancellable *cancellable,
1138 GAsyncReadyCallback callback,
1139 gpointer user_data)
1140 {
1141 g_autoptr(IdeTask) task = NULL;
1142 g_autoptr(GFile) parent = NULL;
1143 g_autofree gchar *name = NULL;
1144 const gchar *project_id;
1145 LoadProject *lp;
1146 GFile *directory;
1147 GFile *file;
1148
1149 IDE_ENTRY;
1150
1151 g_return_if_fail (IDE_IS_MAIN_THREAD ());
1152 g_return_if_fail (IDE_IS_WORKBENCH (self));
1153 g_return_if_fail (IDE_IS_PROJECT_INFO (project_info));
1154 g_return_if_fail (workspace_type != IDE_TYPE_WORKSPACE);
1155 g_return_if_fail (workspace_type == G_TYPE_INVALID ||
1156 g_type_is_a (workspace_type, IDE_TYPE_WORKSPACE));
1157 g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
1158 g_return_if_fail (self->unloaded == FALSE);
1159
1160 task = ide_task_new (self, cancellable, callback, user_data);
1161 ide_task_set_source_tag (task, ide_workbench_load_project_async);
1162
1163 if (self->project_info != NULL)
1164 {
1165 ide_task_return_new_error (task,
1166 G_IO_ERROR,
1167 G_IO_ERROR_FAILED,
1168 "Cannot load project, a project is already loaded");
1169 IDE_EXIT;
1170 }
1171
1172 _ide_context_set_has_project (self->context);
1173
1174 g_set_object (&self->project_info, project_info);
1175
1176 /* Update context project-id based on project-info */
1177 if ((project_id = ide_project_info_get_id (project_info)))
1178 {
1179 g_autofree gchar *generated = ide_create_project_id (project_id);
1180 ide_context_set_project_id (self->context, generated);
1181 }
1182
1183 if (!ide_project_info_get_directory (project_info) &&
1184 !ide_project_info_get_file (project_info))
1185 {
1186 ide_task_return_new_error (task,
1187 G_IO_ERROR,
1188 G_IO_ERROR_NOT_FOUND,
1189 "No file or directory provided to load as project");
1190 IDE_EXIT;
1191 }
1192
1193 /* Fallback to using directory as file if necessary */
1194 if (!(file = ide_project_info_get_file (project_info)))
1195 {
1196 file = ide_project_info_get_directory (project_info);
1197 g_assert (G_IS_FILE (file));
1198
1199 ide_project_info_set_file (project_info, file);
1200 }
1201
1202 /*
1203 * Track the directory root based on project info. If we didn't get a
1204 * directory set, then take the parent of the project file.
1205 */
1206
1207 if ((directory = ide_project_info_get_directory (project_info)))
1208 {
1209 ide_context_set_workdir (self->context, directory);
1210 }
1211 else
1212 {
1213 if (g_file_query_file_type (file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) == G_FILE_TYPE_DIRECTORY)
1214 {
1215 ide_context_set_workdir (self->context, file);
1216 directory = file;
1217 }
1218 else
1219 {
1220 ide_context_set_workdir (self->context, (parent = g_file_get_parent (file)));
1221 directory = parent;
1222 }
1223
1224 ide_project_info_set_directory (project_info, directory);
1225 }
1226
1227 g_assert (G_IS_FILE (directory));
1228
1229 name = g_file_get_basename (directory);
1230 ide_context_set_title (self->context, name);
1231
1232 {
1233 GFile *pdir = ide_project_info_get_directory (project_info);
1234 GFile *pfile = ide_project_info_get_file (project_info);
1235 const gchar *pident = ide_project_info_get_id (project_info);
1236 const gchar *pname = ide_project_info_get_name (project_info);
1237
1238 /* Log some information to help track down project loading issues. */
1239 g_debug ("Loading project");
1240 g_debug (" id = %s", pname);
1241 g_debug (" name = %s", pident);
1242 g_debug (" dir = %s", g_file_peek_path (pdir));
1243 g_debug (" file = %s", g_file_peek_path (pfile));
1244 }
1245
1246 /* If there has not been a project name set, make the default matching
1247 * the directory name. A plugin may update the name with more information
1248 * based on .doap files, etc.
1249 */
1250 if (!ide_project_info_get_name (project_info))
1251 ide_project_info_set_name (project_info, name);
1252
1253 /* Setup some information we're going to need later on when loading the
1254 * individual workbench addins (and then creating the workspace).
1255 */
1256 lp = g_slice_new0 (LoadProject);
1257 lp->project_info = g_object_ref (project_info);
1258 /* HACK: Workaround for lack of last event time */
1259 lp->present_time = g_get_monotonic_time () / 1000L;
1260 lp->addins = ide_workbench_collect_addins (self);
1261 lp->workspace_type = workspace_type;
1262 ide_task_set_task_data (task, lp, load_project_free);
1263
1264 /*
1265 * Before we load any addins, we want to register the Foundry subsystems
1266 * such as the device manager, diagnostics engine, configurations, etc.
1267 * This makes sure that we have some basics setup before addins load.
1268 */
1269 _ide_foundry_init_async (self->context,
1270 cancellable,
1271 ide_workbench_init_foundry_cb,
1272 g_steal_pointer (&task));
1273
1274 IDE_EXIT;
1275 }
1276
1277 /**
1278 * ide_workbench_load_project_finish:
1279 * @self: a #IdeWorkbench
1280 *
1281 * Completes an asynchronous request to open a project using
1282 * ide_workbench_load_project_async().
1283 *
1284 * Returns: %TRUE if the project was successfully opened; otherwise %FALSE
1285 * and @error is set.
1286 *
1287 * Since: 3.32
1288 */
1289 gboolean
ide_workbench_load_project_finish(IdeWorkbench * self,GAsyncResult * result,GError ** error)1290 ide_workbench_load_project_finish (IdeWorkbench *self,
1291 GAsyncResult *result,
1292 GError **error)
1293 {
1294 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
1295 g_return_val_if_fail (IDE_IS_WORKBENCH (self), FALSE);
1296 g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
1297
1298 return ide_task_propagate_boolean (IDE_TASK (result), error);
1299 }
1300
1301 static void
print_object_tree(IdeObject * object,gpointer depthptr)1302 print_object_tree (IdeObject *object,
1303 gpointer depthptr)
1304 {
1305 gint depth = GPOINTER_TO_INT (depthptr);
1306 g_autofree gchar *space = g_strnfill (depth * 2, ' ');
1307 g_autofree gchar *info = ide_object_repr (object);
1308
1309 g_print ("%s%s\n", space, info);
1310 ide_object_foreach (object,
1311 (GFunc)print_object_tree,
1312 GINT_TO_POINTER (depth + 1));
1313 }
1314
1315 static void
ide_workbench_action_object_tree(IdeWorkbench * self,GVariant * param)1316 ide_workbench_action_object_tree (IdeWorkbench *self,
1317 GVariant *param)
1318 {
1319 g_assert (IDE_IS_WORKBENCH (self));
1320
1321 print_object_tree (IDE_OBJECT (self->context), NULL);
1322 }
1323
1324 static void
ide_workbench_action_dump_tasks(IdeWorkbench * self,GVariant * param)1325 ide_workbench_action_dump_tasks (IdeWorkbench *self,
1326 GVariant *param)
1327 {
1328 g_assert (IDE_IS_WORKBENCH (self));
1329
1330 _ide_dump_tasks ();
1331 }
1332
1333 static void
ide_workbench_action_inspector(IdeWorkbench * self,GVariant * param)1334 ide_workbench_action_inspector (IdeWorkbench *self,
1335 GVariant *param)
1336 {
1337 gtk_window_set_interactive_debugging (TRUE);
1338 }
1339
1340 static void
ide_workbench_action_close_cb(GObject * object,GAsyncResult * result,gpointer user_data)1341 ide_workbench_action_close_cb (GObject *object,
1342 GAsyncResult *result,
1343 gpointer user_data)
1344 {
1345 IdeWorkbench *self = (IdeWorkbench *)object;
1346
1347 g_assert (IDE_IS_MAIN_THREAD ());
1348 g_assert (IDE_IS_WORKBENCH (self));
1349 g_assert (G_IS_ASYNC_RESULT (result));
1350 g_assert (user_data == NULL);
1351
1352 if (ide_workbench_unload_finish (self, result, NULL))
1353 {
1354 IdeApplication *app = IDE_APPLICATION_DEFAULT;
1355 GtkWindow *active;
1356
1357 if (!(active = gtk_application_get_active_window (GTK_APPLICATION (app))))
1358 g_application_activate (G_APPLICATION (app));
1359 else
1360 ide_gtk_window_present (active);
1361 }
1362 }
1363
1364 static void
ide_workbench_action_close(IdeWorkbench * self,GVariant * param)1365 ide_workbench_action_close (IdeWorkbench *self,
1366 GVariant *param)
1367 {
1368 g_assert (IDE_IS_WORKBENCH (self));
1369 g_assert (param == NULL);
1370
1371 if (self->unloaded == FALSE)
1372 ide_workbench_unload_async (self,
1373 NULL,
1374 ide_workbench_action_close_cb,
1375 NULL);
1376 }
1377
1378 static void
ide_workbench_action_reload_all(IdeWorkbench * self,GVariant * param)1379 ide_workbench_action_reload_all (IdeWorkbench *self,
1380 GVariant *param)
1381 {
1382 IdeBufferManager *bufmgr;
1383 IdeContext *context;
1384
1385 g_assert (IDE_IS_WORKBENCH (self));
1386 g_assert (param == NULL);
1387
1388 context = ide_workbench_get_context (self);
1389 bufmgr = ide_buffer_manager_from_context (context);
1390 ide_buffer_manager_reload_all_async (bufmgr, NULL, NULL, NULL);
1391 }
1392
1393 static void
ide_workbench_action_open(IdeWorkbench * self,GVariant * param)1394 ide_workbench_action_open (IdeWorkbench *self,
1395 GVariant *param)
1396 {
1397 GtkFileChooserNative *chooser;
1398 IdeWorkspace *workspace;
1399 gint ret;
1400
1401 g_assert (IDE_IS_WORKBENCH (self));
1402 g_assert (param == NULL);
1403
1404 workspace = ide_workbench_get_current_workspace (self);
1405
1406 chooser = gtk_file_chooser_native_new (_("Open File…"),
1407 GTK_WINDOW (workspace),
1408 GTK_FILE_CHOOSER_ACTION_OPEN,
1409 _("_Open"),
1410 _("_Cancel"));
1411 gtk_native_dialog_set_modal (GTK_NATIVE_DIALOG (chooser), FALSE);
1412 gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (chooser), FALSE);
1413 gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (chooser), TRUE);
1414
1415 ret = gtk_native_dialog_run (GTK_NATIVE_DIALOG (chooser));
1416
1417 if (ret == GTK_RESPONSE_ACCEPT)
1418 {
1419 g_autoslist(GFile) files = gtk_file_chooser_get_files (GTK_FILE_CHOOSER (chooser));
1420
1421 for (const GSList *iter = files; iter; iter = iter->next)
1422 {
1423 GFile *file = iter->data;
1424
1425 g_assert (G_IS_FILE (file));
1426
1427 ide_workbench_open_async (self, file, NULL, 0, NULL, NULL, NULL);
1428 }
1429 }
1430
1431 gtk_native_dialog_destroy (GTK_NATIVE_DIALOG (chooser));
1432 }
1433
1434 /**
1435 * ide_workbench_get_search_engine:
1436 * @self: a #IdeWorkbench
1437 *
1438 * Gets the search engine for the workbench, if any.
1439 *
1440 * Returns: (transfer none): an #IdeSearchEngine
1441 *
1442 * Since: 3.32
1443 */
1444 IdeSearchEngine *
ide_workbench_get_search_engine(IdeWorkbench * self)1445 ide_workbench_get_search_engine (IdeWorkbench *self)
1446 {
1447 g_return_val_if_fail (IDE_IS_WORKBENCH (self), NULL);
1448 g_return_val_if_fail (self->context != NULL, NULL);
1449
1450 if (self->search_engine == NULL)
1451 self->search_engine = ide_object_ensure_child_typed (IDE_OBJECT (self->context),
1452 IDE_TYPE_SEARCH_ENGINE);
1453
1454 return self->search_engine;
1455 }
1456
1457 /**
1458 * ide_workbench_get_project_info:
1459 * @self: a #IdeWorkbench
1460 *
1461 * Gets the #IdeProjectInfo for the workbench, if a project has been or is
1462 * currently, loading.
1463 *
1464 * Returns: (transfer none) (nullable): an #IdeProjectInfo or %NULL
1465 *
1466 * Since: 3.32
1467 */
1468 IdeProjectInfo *
ide_workbench_get_project_info(IdeWorkbench * self)1469 ide_workbench_get_project_info (IdeWorkbench *self)
1470 {
1471 g_return_val_if_fail (IDE_IS_WORKBENCH (self), NULL);
1472
1473 return self->project_info;
1474 }
1475
1476 static void
ide_workbench_unload_foundry_cb(GObject * object,GAsyncResult * result,gpointer user_data)1477 ide_workbench_unload_foundry_cb (GObject *object,
1478 GAsyncResult *result,
1479 gpointer user_data)
1480 {
1481 g_autoptr(IdeTask) task = user_data;
1482 g_autoptr(GError) error = NULL;
1483 IdeWorkbench *self;
1484
1485 g_assert (G_IS_ASYNC_RESULT (result));
1486 g_assert (IDE_IS_TASK (task));
1487
1488 self = ide_task_get_source_object (task);
1489
1490 if (!_ide_foundry_unload_finish (result, &error))
1491 ide_task_return_error (task, g_steal_pointer (&error));
1492 else
1493 ide_task_return_boolean (task, TRUE);
1494
1495 if (self->context != NULL)
1496 {
1497 ide_object_destroy (IDE_OBJECT (self->context));
1498 g_clear_object (&self->context);
1499 }
1500 }
1501
1502 static void
ide_workbench_unload_project_completed(IdeWorkbench * self,IdeTask * task)1503 ide_workbench_unload_project_completed (IdeWorkbench *self,
1504 IdeTask *task)
1505 {
1506 g_assert (IDE_IS_WORKBENCH (self));
1507 g_assert (IDE_IS_TASK (task));
1508
1509 g_clear_object (&self->addins);
1510 ide_workbench_foreach_workspace (self, (GtkCallback)gtk_widget_destroy, NULL);
1511
1512 _ide_foundry_unload_async (self->context,
1513 ide_task_get_cancellable (task),
1514 ide_workbench_unload_foundry_cb,
1515 g_object_ref (task));
1516 }
1517 static void
ide_workbench_unload_project_cb(GObject * object,GAsyncResult * result,gpointer user_data)1518 ide_workbench_unload_project_cb (GObject *object,
1519 GAsyncResult *result,
1520 gpointer user_data)
1521 {
1522 IdeWorkbenchAddin *addin = (IdeWorkbenchAddin *)object;
1523 g_autoptr(IdeTask) task = user_data;
1524 g_autoptr(GError) error = NULL;
1525 IdeWorkbench *self;
1526 GPtrArray *addins;
1527
1528 g_assert (IDE_IS_WORKBENCH_ADDIN (addin));
1529 g_assert (G_IS_ASYNC_RESULT (result));
1530 g_assert (IDE_IS_TASK (task));
1531
1532 self = ide_task_get_source_object (task);
1533 addins = ide_task_get_task_data (task);
1534
1535 g_assert (IDE_IS_WORKBENCH (self));
1536 g_assert (addins != NULL);
1537 g_assert (addins->len > 0);
1538
1539 if (!ide_workbench_addin_unload_project_finish (addin, result, &error))
1540 {
1541 if (!ignore_error (error))
1542 g_warning ("%s failed to unload project: %s",
1543 G_OBJECT_TYPE_NAME (addin), error->message);
1544 }
1545
1546 g_ptr_array_remove (addins, addin);
1547
1548 if (addins->len == 0)
1549 ide_workbench_unload_project_completed (self, task);
1550 }
1551
1552 /**
1553 * ide_workbench_unload_async:
1554 * @self: an #IdeWorkbench
1555 * @cancellable: (nullable): a #GCancellable
1556 * @callback: a #GAsyncReadyCallback to execute upon completion
1557 * @user_data: closure data for @callback
1558 *
1559 * Asynchronously unloads the workbench.
1560 *
1561 * All #IdeWorkspace windows will be closed after calling this
1562 * function.
1563 *
1564 * Since: 3.32
1565 */
1566 void
ide_workbench_unload_async(IdeWorkbench * self,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1567 ide_workbench_unload_async (IdeWorkbench *self,
1568 GCancellable *cancellable,
1569 GAsyncReadyCallback callback,
1570 gpointer user_data)
1571 {
1572 g_autoptr(IdeTask) task = NULL;
1573 g_autoptr(GPtrArray) addins = NULL;
1574 GApplication *app;
1575
1576 g_return_if_fail (IDE_IS_WORKBENCH (self));
1577 g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
1578
1579 task = ide_task_new (self, cancellable, callback, user_data);
1580 ide_task_set_source_tag (task, ide_workbench_unload_async);
1581
1582 if (self->unloaded)
1583 {
1584 ide_task_return_boolean (task, TRUE);
1585 return;
1586 }
1587
1588 self->unloaded = TRUE;
1589
1590 /* Keep the GApplication alive for the lifetime of the task */
1591 app = g_application_get_default ();
1592 g_signal_connect_object (task,
1593 "notify::completed",
1594 G_CALLBACK (g_application_release),
1595 app,
1596 G_CONNECT_SWAPPED);
1597 g_application_hold (app);
1598
1599 /*
1600 * Remove our workbench from the application, so that no new
1601 * open-file requests can keep us alive while we're shutting
1602 * down.
1603 */
1604
1605 ide_application_remove_workbench (IDE_APPLICATION (app), self);
1606
1607 /* If we haven't loaded a project, then there is nothing to
1608 * do right now, just let ide_workbench_addin_unload() be called
1609 * when the workbench disposes.
1610 */
1611 if (self->project_info == NULL)
1612 {
1613 ide_workbench_unload_project_completed (self, g_steal_pointer (&task));
1614 return;
1615 }
1616
1617 addins = ide_workbench_collect_addins (self);
1618 ide_task_set_task_data (task, g_ptr_array_ref (addins), g_ptr_array_unref);
1619
1620 if (addins->len == 0)
1621 {
1622 ide_workbench_unload_project_completed (self, task);
1623 return;
1624 }
1625
1626 for (guint i = 0; i < addins->len; i++)
1627 {
1628 IdeWorkbenchAddin *addin = g_ptr_array_index (addins, i);
1629
1630 ide_workbench_addin_unload_project_async (addin,
1631 self->project_info,
1632 ide_task_get_cancellable (task),
1633 ide_workbench_unload_project_cb,
1634 g_object_ref (task));
1635 }
1636
1637 /* Since the g_steal_pointer() just before doesn't always run, ensure the
1638 * task isn't freed while it hasn't yet finished running asynchronously.
1639 */
1640 task = NULL;
1641 }
1642
1643 /**
1644 * ide_workbench_unload_finish:
1645 * @self: an #IdeWorkbench
1646 * @result: a #GAsyncResult provided to callback
1647 * @error: a location for a #GError, or %NULL
1648
1649 * Completes a request to unload the workbench.
1650 *
1651 * Returns: %TRUE if the workbench was unloaded successfully,
1652 * otherwise %FALSE and @error is set.
1653 *
1654 * Since: 3.32
1655 */
1656 gboolean
ide_workbench_unload_finish(IdeWorkbench * self,GAsyncResult * result,GError ** error)1657 ide_workbench_unload_finish (IdeWorkbench *self,
1658 GAsyncResult *result,
1659 GError **error)
1660 {
1661 g_return_val_if_fail (IDE_IS_WORKBENCH (self), FALSE);
1662 g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
1663
1664 return ide_task_propagate_boolean (IDE_TASK (result), error);
1665 }
1666
1667 static void
ide_workbench_open_all_cb(GObject * object,GAsyncResult * result,gpointer user_data)1668 ide_workbench_open_all_cb (GObject *object,
1669 GAsyncResult *result,
1670 gpointer user_data)
1671 {
1672 IdeWorkbench *self = (IdeWorkbench *)object;
1673 g_autoptr(IdeTask) task = user_data;
1674 g_autoptr(GError) error = NULL;
1675 gint *n_active;
1676
1677 g_assert (IDE_IS_WORKBENCH (self));
1678 g_assert (G_IS_ASYNC_RESULT (result));
1679 g_assert (IDE_IS_TASK (task));
1680
1681 if (!ide_workbench_open_finish (self, result, &error))
1682 g_message ("Failed to open file: %s", error->message);
1683
1684 n_active = ide_task_get_task_data (task);
1685 g_assert (n_active != NULL);
1686
1687 (*n_active)--;
1688
1689 if (*n_active == 0)
1690 ide_task_return_boolean (task, TRUE);
1691 }
1692
1693 /**
1694 * ide_workbench_open_all_async:
1695 * @self: an #IdeWorkbench
1696 * @files: (array length=n_files): an array of #GFile
1697 * @n_files: number of #GFiles contained in @files
1698 * @hint: (nullable): an optional hint about what addin to use
1699 * @cancellable: (nullable): a #GCancellable
1700 * @callback: a #GAsyncReadyCallback to execute upon completion
1701 * @user_data: closure data for @callback
1702 *
1703 * Requests that the workbench open all of the #GFile denoted by @files.
1704 *
1705 * If @hint is provided, that will be used to determine what workbench
1706 * addin to use when opening the file. The @hint name should match the
1707 * module name of the plugin.
1708 *
1709 * Call ide_workbench_open_finish() from @callback to complete this
1710 * operation.
1711 *
1712 * Since: 3.32
1713 */
1714 void
ide_workbench_open_all_async(IdeWorkbench * self,GFile ** files,guint n_files,const gchar * hint,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1715 ide_workbench_open_all_async (IdeWorkbench *self,
1716 GFile **files,
1717 guint n_files,
1718 const gchar *hint,
1719 GCancellable *cancellable,
1720 GAsyncReadyCallback callback,
1721 gpointer user_data)
1722 {
1723 g_autoptr(IdeTask) task = NULL;
1724 g_autoptr(GPtrArray) ar = NULL;
1725 gint *n_active;
1726
1727 g_return_if_fail (IDE_IS_WORKBENCH (self));
1728 g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
1729
1730 task = ide_task_new (self, cancellable, callback, user_data);
1731 ide_task_set_source_tag (task, ide_workbench_open_all_async);
1732
1733 if (n_files == 0)
1734 {
1735 ide_task_return_boolean (task, TRUE);
1736 return;
1737 }
1738
1739 ar = g_ptr_array_new_full (n_files, g_object_unref);
1740 for (guint i = 0; i < n_files; i++)
1741 g_ptr_array_add (ar, g_object_ref (files[i]));
1742
1743 n_active = g_new0 (gint, 1);
1744 *n_active = ar->len;
1745 ide_task_set_task_data (task, n_active, g_free);
1746
1747 for (guint i = 0; i < ar->len; i++)
1748 {
1749 GFile *file = g_ptr_array_index (ar, i);
1750
1751 ide_workbench_open_async (self,
1752 file,
1753 hint,
1754 IDE_BUFFER_OPEN_FLAGS_NONE,
1755 cancellable,
1756 ide_workbench_open_all_cb,
1757 g_object_ref (task));
1758 }
1759 }
1760
1761 /**
1762 * ide_workbench_open_async:
1763 * @self: an #IdeWorkbench
1764 * @file: a #GFile
1765 * @hint: (nullable): an optional hint about what addin to use
1766 * @flags: optional flags when opening the file
1767 * @cancellable: (nullable): a #GCancellable
1768 * @callback: a #GAsyncReadyCallback to execute upon completion
1769 * @user_data: closure data for @callback
1770 *
1771 * Requests that the workbench open @file.
1772 *
1773 * If @hint is provided, that will be used to determine what workbench
1774 * addin to use when opening the file. The @hint name should match the
1775 * module name of the plugin.
1776 *
1777 * @flags may be ignored by some backends.
1778 *
1779 * Since: 3.32
1780 */
1781 void
ide_workbench_open_async(IdeWorkbench * self,GFile * file,const gchar * hint,IdeBufferOpenFlags flags,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1782 ide_workbench_open_async (IdeWorkbench *self,
1783 GFile *file,
1784 const gchar *hint,
1785 IdeBufferOpenFlags flags,
1786 GCancellable *cancellable,
1787 GAsyncReadyCallback callback,
1788 gpointer user_data)
1789 {
1790 g_return_if_fail (IDE_IS_WORKBENCH (self));
1791 g_return_if_fail (G_IS_FILE (file));
1792 g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
1793
1794 ide_workbench_open_at_async (self,
1795 file,
1796 hint,
1797 -1,
1798 -1,
1799 flags,
1800 cancellable,
1801 callback,
1802 user_data);
1803 }
1804
1805 static void
ide_workbench_open_cb(GObject * object,GAsyncResult * result,gpointer user_data)1806 ide_workbench_open_cb (GObject *object,
1807 GAsyncResult *result,
1808 gpointer user_data)
1809 {
1810 IdeWorkbenchAddin *addin = (IdeWorkbenchAddin *)object;
1811 IdeWorkbenchAddin *next;
1812 g_autoptr(IdeTask) task = user_data;
1813 g_autoptr(GError) error = NULL;
1814 GCancellable *cancellable;
1815 Open *o;
1816
1817 g_assert (IDE_IS_WORKBENCH_ADDIN (addin));
1818 g_assert (G_IS_ASYNC_RESULT (result));
1819 g_assert (IDE_IS_TASK (task));
1820
1821 cancellable = ide_task_get_cancellable (task);
1822 o = ide_task_get_task_data (task);
1823
1824 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
1825 g_assert (o != NULL);
1826 g_assert (o->addins != NULL);
1827 g_assert (o->addins->len > 0);
1828
1829 if (ide_workbench_addin_open_finish (addin, result, &error))
1830 {
1831 ide_task_return_boolean (task, TRUE);
1832 return;
1833 }
1834
1835 g_debug ("%s did not open the file, trying next.",
1836 G_OBJECT_TYPE_NAME (addin));
1837
1838 g_ptr_array_remove (o->addins, addin);
1839
1840 /*
1841 * We failed to open the file, try the next addin that is
1842 * left which said it supported the content-type.
1843 */
1844
1845 if (o->addins->len == 0)
1846 {
1847 ide_task_return_new_error (task,
1848 G_IO_ERROR,
1849 G_IO_ERROR_FAILED,
1850 "Failed to locate addin supporting file");
1851 return;
1852 }
1853
1854 next = g_ptr_array_index (o->addins, 0);
1855
1856 ide_workbench_addin_open_at_async (next,
1857 o->file,
1858 o->content_type,
1859 o->at_line,
1860 o->at_line_offset,
1861 o->flags,
1862 cancellable,
1863 ide_workbench_open_cb,
1864 g_steal_pointer (&task));
1865 }
1866
1867 static gint
sort_by_priority(gconstpointer a,gconstpointer b,gpointer user_data)1868 sort_by_priority (gconstpointer a,
1869 gconstpointer b,
1870 gpointer user_data)
1871 {
1872 IdeWorkbenchAddin *addin_a = *(IdeWorkbenchAddin **)a;
1873 IdeWorkbenchAddin *addin_b = *(IdeWorkbenchAddin **)b;
1874 Open *o = user_data;
1875 gint prio_a = 0;
1876 gint prio_b = 0;
1877
1878 if (!ide_workbench_addin_can_open (addin_a, o->file, o->content_type, &prio_a))
1879 return 1;
1880
1881 if (!ide_workbench_addin_can_open (addin_b, o->file, o->content_type, &prio_b))
1882 return -1;
1883
1884 if (prio_a < prio_b)
1885 return -1;
1886 else if (prio_a > prio_b)
1887 return 1;
1888 else
1889 return 0;
1890 }
1891
1892 static void
ide_workbench_open_query_info_cb(GObject * object,GAsyncResult * result,gpointer user_data)1893 ide_workbench_open_query_info_cb (GObject *object,
1894 GAsyncResult *result,
1895 gpointer user_data)
1896 {
1897 GFile *file = (GFile *)object;
1898 g_autoptr(IdeTask) task = user_data;
1899 g_autoptr(GFileInfo) info = NULL;
1900 g_autoptr(GError) error = NULL;
1901 IdeWorkbenchAddin *first;
1902 GCancellable *cancellable;
1903 Open *o;
1904
1905 g_assert (G_IS_FILE (file));
1906 g_assert (G_IS_ASYNC_RESULT (result));
1907 g_assert (IDE_IS_TASK (task));
1908
1909 cancellable = ide_task_get_cancellable (task);
1910 o = ide_task_get_task_data (task);
1911
1912 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
1913 g_assert (o != NULL);
1914 g_assert (o->addins != NULL);
1915 g_assert (o->addins->len > 0);
1916
1917 if ((info = g_file_query_info_finish (file, result, &error)))
1918 o->content_type = g_strdup (g_file_info_get_content_type (info));
1919
1920 /* Remove unsupported addins while iterating backwards so that
1921 * we can preserve the ordering of the array as we go.
1922 */
1923 for (guint i = o->addins->len; i > 0; i--)
1924 {
1925 IdeWorkbenchAddin *addin = g_ptr_array_index (o->addins, i - 1);
1926 gint prio = G_MAXINT;
1927
1928 if (!ide_workbench_addin_can_open (addin, o->file, o->content_type, &prio))
1929 {
1930 g_ptr_array_remove_index_fast (o->addins, i - 1);
1931 if (o->preferred == addin)
1932 g_clear_object (&o->preferred);
1933 }
1934 }
1935
1936 if (o->addins->len == 0)
1937 {
1938 ide_task_return_new_error (task,
1939 G_IO_ERROR,
1940 G_IO_ERROR_FAILED,
1941 "No addins can open the file");
1942 return;
1943 }
1944
1945 /*
1946 * Now sort the addins by priority, so that we can attempt to load them
1947 * in the preferred ordering.
1948 */
1949 g_ptr_array_sort_with_data (o->addins, sort_by_priority, o);
1950
1951 /*
1952 * Ensure that we place the preferred at the head of the array, so
1953 * that it gets preference over default priorities.
1954 */
1955 if (o->preferred != NULL)
1956 {
1957 g_ptr_array_insert (o->addins, 0, g_object_ref (o->preferred));
1958
1959 for (guint i = 1; i < o->addins->len; i++)
1960 {
1961 if (g_ptr_array_index (o->addins, i) == (gpointer)o->preferred)
1962 {
1963 g_ptr_array_remove_index (o->addins, i);
1964 break;
1965 }
1966 }
1967 }
1968
1969 /* Now start requesting that addins attempt to load the file. */
1970
1971 first = g_ptr_array_index (o->addins, 0);
1972
1973 ide_workbench_addin_open_at_async (first,
1974 o->file,
1975 o->content_type,
1976 o->at_line,
1977 o->at_line_offset,
1978 o->flags,
1979 cancellable,
1980 ide_workbench_open_cb,
1981 g_steal_pointer (&task));
1982 }
1983
1984 /**
1985 * ide_workbench_open_at_async:
1986 * @self: an #IdeWorkbench
1987 * @file: a #GFile
1988 * @hint: (nullable): an optional hint about what addin to use
1989 * @at_line: the line number to open at, or -1 to ignore
1990 * @at_line_offset: the line offset to open at, or -1 to ignore
1991 * @flags: optional #IdeBufferOpenFlags
1992 * @cancellable: (nullable): a #GCancellable
1993 * @callback: a #GAsyncReadyCallback to execute upon completion
1994 * @user_data: closure data for @callback
1995 *
1996 * Like ide_workbench_open_async(), this allows opening a file
1997 * within the workbench. However, it also allows specifying a
1998 * line and column offset within the file to focus. Usually, this
1999 * only makes sense for files that can be opened in an editor.
2000 *
2001 * @at_line and @at_line_offset may be < 0 to ignore the parameters.
2002 *
2003 * @flags may be ignored by some backends
2004 *
2005 * Use ide_workbench_open_finish() to receive teh result of this
2006 * asynchronous operation.
2007 *
2008 * Since: 3.32
2009 */
2010 void
ide_workbench_open_at_async(IdeWorkbench * self,GFile * file,const gchar * hint,gint at_line,gint at_line_offset,IdeBufferOpenFlags flags,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)2011 ide_workbench_open_at_async (IdeWorkbench *self,
2012 GFile *file,
2013 const gchar *hint,
2014 gint at_line,
2015 gint at_line_offset,
2016 IdeBufferOpenFlags flags,
2017 GCancellable *cancellable,
2018 GAsyncReadyCallback callback,
2019 gpointer user_data)
2020 {
2021 g_autoptr(IdeTask) task = NULL;
2022 g_autoptr(GPtrArray) addins = NULL;
2023 IdeWorkbench *other;
2024 Open *o;
2025
2026 g_return_if_fail (IDE_IS_WORKBENCH (self));
2027 g_return_if_fail (G_IS_FILE (file));
2028 g_return_if_fail (self->unloaded == FALSE);
2029 g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
2030
2031 /* Possibly re-route opening the file to another workbench if we
2032 * discover the file is a better fit over there.
2033 */
2034 other = ide_application_find_workbench_for_file (IDE_APPLICATION_DEFAULT, file);
2035
2036 if (other != NULL && other != self)
2037 {
2038 ide_workbench_open_at_async (other,
2039 file,
2040 hint,
2041 at_line,
2042 at_line_offset,
2043 flags,
2044 cancellable,
2045 callback,
2046 user_data);
2047 return;
2048 }
2049
2050 /* Canonicalize parameters */
2051 if (at_line < 0)
2052 at_line = -1;
2053 if (at_line_offset < 0)
2054 at_line_offset = -1;
2055
2056 task = ide_task_new (self, cancellable, callback, user_data);
2057 ide_task_set_source_tag (task, ide_workbench_open_at_async);
2058
2059 /*
2060 * Make sure we might have an addin to load after discovering
2061 * the files content-type.
2062 */
2063 if (!(addins = ide_workbench_collect_addins (self)) || addins->len == 0)
2064 {
2065 ide_task_return_new_error (task,
2066 G_IO_ERROR,
2067 G_IO_ERROR_FAILED,
2068 "No addins could open the file");
2069 return;
2070 }
2071
2072 o = g_slice_new0 (Open);
2073 o->addins = g_ptr_array_ref (addins);
2074 if (hint != NULL)
2075 o->preferred = ide_workbench_find_addin (self, hint);
2076 o->file = g_object_ref (file);
2077 o->hint = g_strdup (hint);
2078 o->flags = flags;
2079 o->at_line = at_line;
2080 o->at_line_offset = at_line_offset;
2081 ide_task_set_task_data (task, o, open_free);
2082
2083 g_file_query_info_async (file,
2084 G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
2085 G_FILE_QUERY_INFO_NONE,
2086 G_PRIORITY_DEFAULT,
2087 cancellable,
2088 ide_workbench_open_query_info_cb,
2089 g_steal_pointer (&task));
2090 }
2091
2092 /**
2093 * ide_workbench_open_finish:
2094 * @self: an #IdeWorkbench
2095 * @result: a #GAsyncResult provided to callback
2096 * @error: a location for a #GError, or %NULL
2097 *
2098 * Completes a request to open a file using either
2099 * ide_workbench_open_async() or ide_workbench_open_at_async().
2100 *
2101 * Returns: %TRUE if the file was successfully opened; otherwise
2102 * %FALSE and @error is set.
2103 *
2104 * Since: 3.32
2105 */
2106 gboolean
ide_workbench_open_finish(IdeWorkbench * self,GAsyncResult * result,GError ** error)2107 ide_workbench_open_finish (IdeWorkbench *self,
2108 GAsyncResult *result,
2109 GError **error)
2110 {
2111 g_return_val_if_fail (IDE_IS_WORKBENCH (self), FALSE);
2112 g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
2113
2114 return ide_task_propagate_boolean (IDE_TASK (result), error);
2115 }
2116
2117 /**
2118 * ide_workbench_get_current_workspace:
2119 * @self: a #IdeWorkbench
2120 *
2121 * Gets the most recently focused workspace, which may be used to
2122 * deliver events such as opening new pages.
2123 *
2124 * Returns: (transfer none) (nullable): an #IdeWorkspace or %NULL
2125 *
2126 * Since: 3.32
2127 */
2128 IdeWorkspace *
ide_workbench_get_current_workspace(IdeWorkbench * self)2129 ide_workbench_get_current_workspace (IdeWorkbench *self)
2130 {
2131 g_return_val_if_fail (IDE_IS_WORKBENCH (self), NULL);
2132
2133 if (self->mru_queue.length > 0)
2134 return IDE_WORKSPACE (self->mru_queue.head->data);
2135
2136 return NULL;
2137 }
2138
2139 /**
2140 * ide_workbench_activate:
2141 * @self: a #IdeWorkbench
2142 *
2143 * This function will attempt to raise the most recently focused workspace.
2144 *
2145 * Since: 3.32
2146 */
2147 void
ide_workbench_activate(IdeWorkbench * self)2148 ide_workbench_activate (IdeWorkbench *self)
2149 {
2150 IdeWorkspace *workspace;
2151
2152 g_return_if_fail (IDE_IS_WORKBENCH (self));
2153
2154 if ((workspace = ide_workbench_get_current_workspace (self)))
2155 ide_workbench_focus_workspace (self, workspace);
2156 }
2157
2158 static void
ide_workbench_propagate_vcs_cb(PeasExtensionSet * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)2159 ide_workbench_propagate_vcs_cb (PeasExtensionSet *set,
2160 PeasPluginInfo *plugin_info,
2161 PeasExtension *exten,
2162 gpointer user_data)
2163 {
2164 IdeWorkbenchAddin *addin = (IdeWorkbenchAddin *)exten;
2165 IdeVcs *vcs = user_data;
2166
2167 g_assert (PEAS_IS_EXTENSION_SET (set));
2168 g_assert (plugin_info != NULL);
2169 g_assert (IDE_IS_WORKBENCH_ADDIN (addin));
2170 g_assert (!vcs || IDE_IS_VCS (vcs));
2171
2172 ide_workbench_addin_vcs_changed (addin, vcs);
2173 }
2174
2175 /**
2176 * ide_workbench_get_vcs:
2177 * @self: a #IdeWorkbench
2178 *
2179 * Gets the #IdeVcs that has been loaded for the workbench, if any.
2180 *
2181 * Returns: (transfer none) (nullable): an #IdeVcs or %NULL
2182 *
2183 * Since: 3.32
2184 */
2185 IdeVcs *
ide_workbench_get_vcs(IdeWorkbench * self)2186 ide_workbench_get_vcs (IdeWorkbench *self)
2187 {
2188 g_return_val_if_fail (IDE_IS_WORKBENCH (self), NULL);
2189
2190 return self->vcs;
2191 }
2192
2193 /**
2194 * ide_workbench_get_vcs_monitor:
2195 * @self: a #IdeWorkbench
2196 *
2197 * Gets the #IdeVcsMonitor for the workbench, if any.
2198 *
2199 * Returns: (transfer none) (nullable): an #IdeVcsMonitor or %NULL
2200 *
2201 * Since: 3.32
2202 */
2203 IdeVcsMonitor *
ide_workbench_get_vcs_monitor(IdeWorkbench * self)2204 ide_workbench_get_vcs_monitor (IdeWorkbench *self)
2205 {
2206 g_return_val_if_fail (IDE_IS_WORKBENCH (self), NULL);
2207
2208 return self->vcs_monitor;
2209 }
2210
2211 static void
remove_non_matching_vcs_cb(IdeObject * child,IdeVcs * vcs)2212 remove_non_matching_vcs_cb (IdeObject *child,
2213 IdeVcs *vcs)
2214 {
2215 g_assert (IDE_IS_MAIN_THREAD ());
2216 g_assert (IDE_IS_OBJECT (child));
2217 g_assert (IDE_IS_VCS (vcs));
2218
2219 if (IDE_IS_VCS (child) && IDE_VCS (child) != vcs)
2220 ide_object_destroy (child);
2221 }
2222
2223 static void
ide_workbench_vcs_notify_branch_name_cb(IdeWorkbench * self,GParamSpec * pspec,IdeVcs * vcs)2224 ide_workbench_vcs_notify_branch_name_cb (IdeWorkbench *self,
2225 GParamSpec *pspec,
2226 IdeVcs *vcs)
2227 {
2228 IdeBuildManager *build_manager;
2229
2230 IDE_ENTRY;
2231
2232 g_assert (IDE_IS_WORKBENCH (self));
2233 g_assert (IDE_IS_VCS (vcs));
2234
2235 if (!ide_workbench_has_project (self))
2236 IDE_EXIT;
2237
2238 build_manager = ide_build_manager_from_context (self->context);
2239 ide_build_manager_invalidate (build_manager);
2240
2241 IDE_EXIT;
2242 }
2243
2244 /**
2245 * ide_workbench_set_vcs:
2246 * @self: a #IdeWorkbench
2247 * @vcs: (nullable): an #IdeVcs
2248 *
2249 * Sets the #IdeVcs for the workbench.
2250 *
2251 * Since: 3.32
2252 */
2253 void
ide_workbench_set_vcs(IdeWorkbench * self,IdeVcs * vcs)2254 ide_workbench_set_vcs (IdeWorkbench *self,
2255 IdeVcs *vcs)
2256 {
2257 g_autoptr(IdeVcs) local_vcs = NULL;
2258 g_autoptr(GFile) local_workdir = NULL;
2259 g_autoptr(GFile) workdir = NULL;
2260
2261 g_return_if_fail (IDE_IS_MAIN_THREAD ());
2262 g_return_if_fail (IDE_IS_WORKBENCH (self));
2263 g_return_if_fail (!vcs || IDE_IS_VCS (vcs));
2264
2265 if (self->vcs != NULL && vcs == self->vcs)
2266 return;
2267
2268 if (vcs == NULL)
2269 {
2270 local_workdir = ide_context_ref_workdir (self->context);
2271 vcs = local_vcs = IDE_VCS (ide_directory_vcs_new (local_workdir));
2272 }
2273
2274 g_set_object (&self->vcs, vcs);
2275 ide_object_append (IDE_OBJECT (self->context), IDE_OBJECT (vcs));
2276 ide_object_foreach (IDE_OBJECT (self->context),
2277 (GFunc)remove_non_matching_vcs_cb,
2278 vcs);
2279
2280 if ((workdir = ide_vcs_get_workdir (vcs)))
2281 ide_context_set_workdir (self->context, workdir);
2282
2283 ide_vcs_monitor_set_vcs (self->vcs_monitor, self->vcs);
2284
2285 peas_extension_set_foreach (self->addins,
2286 ide_workbench_propagate_vcs_cb,
2287 self->vcs);
2288
2289 g_signal_connect_object (vcs,
2290 "notify::branch-name",
2291 G_CALLBACK (ide_workbench_vcs_notify_branch_name_cb),
2292 self,
2293 G_CONNECT_SWAPPED);
2294
2295 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_VCS]);
2296 }
2297
2298 /**
2299 * ide_workbench_get_build_system:
2300 * @self: a #IdeWorkbench
2301 *
2302 * Gets the #IdeBuildSystem for the workbench, if any.
2303 *
2304 * Returns: (transfer none) (nullable): an #IdeBuildSystem or %NULL
2305 *
2306 * Since: 3.32
2307 */
2308 IdeBuildSystem *
ide_workbench_get_build_system(IdeWorkbench * self)2309 ide_workbench_get_build_system (IdeWorkbench *self)
2310 {
2311 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
2312 g_return_val_if_fail (IDE_IS_WORKBENCH (self), NULL);
2313
2314 return self->build_system;
2315 }
2316
2317 static void
remove_non_matching_build_systems_cb(IdeObject * child,IdeBuildSystem * build_system)2318 remove_non_matching_build_systems_cb (IdeObject *child,
2319 IdeBuildSystem *build_system)
2320 {
2321 g_assert (IDE_IS_MAIN_THREAD ());
2322 g_assert (IDE_IS_OBJECT (child));
2323 g_assert (IDE_IS_BUILD_SYSTEM (build_system));
2324
2325 if (IDE_IS_BUILD_SYSTEM (child) && IDE_BUILD_SYSTEM (child) != build_system)
2326 ide_object_destroy (child);
2327 }
2328
2329 /**
2330 * ide_workbench_set_build_system:
2331 * @self: a #IdeWorkbench
2332 * @build_system: (nullable): an #IdeBuildSystem or %NULL
2333 *
2334 * Sets the #IdeBuildSystem for the workbench.
2335 *
2336 * If @build_system is %NULL, then a fallback build system will be used
2337 * instead. It does not provide building capabilities, but allows for some
2338 * components that require a build system to continue functioning.
2339 *
2340 * Since: 3.32
2341 */
2342 void
ide_workbench_set_build_system(IdeWorkbench * self,IdeBuildSystem * build_system)2343 ide_workbench_set_build_system (IdeWorkbench *self,
2344 IdeBuildSystem *build_system)
2345 {
2346 g_autoptr(IdeBuildSystem) local_build_system = NULL;
2347 IdeBuildManager *build_manager;
2348
2349 g_return_if_fail (IDE_IS_WORKBENCH (self));
2350 g_return_if_fail (!build_system || IDE_IS_BUILD_SYSTEM (build_system));
2351
2352 if (build_system == self->build_system)
2353 return;
2354
2355 /* We want there to always be a build system available so that various
2356 * plugins don't need lots of extra code to handle the %NULL case. So
2357 * if @build_system is %NULL, then we'll create a fallback build system
2358 * and assign that instead.
2359 */
2360
2361 if (build_system == NULL)
2362 build_system = local_build_system = ide_fallback_build_system_new ();
2363
2364 /* We want to add our new build system before removing the old build
2365 * system to ensure there is always an #IdeBuildSystem child of the
2366 * IdeContext.
2367 */
2368 g_set_object (&self->build_system, build_system);
2369 ide_object_append (IDE_OBJECT (self->context), IDE_OBJECT (build_system));
2370
2371 /* Now remove any previous build-system from the context */
2372 ide_object_foreach (IDE_OBJECT (self->context),
2373 (GFunc)remove_non_matching_build_systems_cb,
2374 build_system);
2375
2376 /* Ask the build-manager to setup a new pipeline */
2377 if ((build_manager = ide_context_peek_child_typed (self->context, IDE_TYPE_BUILD_MANAGER)))
2378 ide_build_manager_invalidate (build_manager);
2379 }
2380
2381 /**
2382 * ide_workbench_get_workspace_by_type:
2383 * @self: a #IdeWorkbench
2384 * @type: a #GType of a subclass of #IdeWorkspace
2385 *
2386 * Gets the most-recently-used workspace that matches @type.
2387 *
2388 * Returns: (transfer none) (nullable): an #IdeWorkspace or %NULL
2389 *
2390 * Since: 3.32
2391 */
2392 IdeWorkspace *
ide_workbench_get_workspace_by_type(IdeWorkbench * self,GType type)2393 ide_workbench_get_workspace_by_type (IdeWorkbench *self,
2394 GType type)
2395 {
2396 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
2397 g_return_val_if_fail (IDE_IS_WORKBENCH (self), NULL);
2398 g_return_val_if_fail (g_type_is_a (type, IDE_TYPE_WORKSPACE), NULL);
2399
2400 for (const GList *iter = self->mru_queue.head; iter; iter = iter->next)
2401 {
2402 if (G_TYPE_CHECK_INSTANCE_TYPE (iter->data, type))
2403 return IDE_WORKSPACE (iter->data);
2404 }
2405
2406 return NULL;
2407 }
2408
2409 gboolean
_ide_workbench_is_last_workspace(IdeWorkbench * self,IdeWorkspace * workspace)2410 _ide_workbench_is_last_workspace (IdeWorkbench *self,
2411 IdeWorkspace *workspace)
2412 {
2413 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
2414 g_return_val_if_fail (IDE_IS_WORKBENCH (self), FALSE);
2415
2416 return self->mru_queue.length == 1 &&
2417 g_queue_peek_head (&self->mru_queue) == (gpointer)workspace;
2418 }
2419
2420 /**
2421 * ide_workbench_has_project:
2422 * @self: a #IdeWorkbench
2423 *
2424 * Returns %TRUE if a project is loaded (or currently loading) in the
2425 * workbench.
2426 *
2427 * Returns: %TRUE if the workbench has a project
2428 *
2429 * Since: 3.32
2430 */
2431 gboolean
ide_workbench_has_project(IdeWorkbench * self)2432 ide_workbench_has_project (IdeWorkbench *self)
2433 {
2434 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
2435 g_return_val_if_fail (IDE_IS_WORKBENCH (self), FALSE);
2436
2437 return self->project_info != NULL;
2438 }
2439
2440 /**
2441 * ide_workbench_addin_find_by_module_name:
2442 * @workbench: an #IdeWorkbench
2443 * @module_name: the name of the addin module
2444 *
2445 * Finds the addin (if any) matching the plugin's @module_name.
2446 *
2447 * Returns: (transfer none) (nullable): an #IdeWorkbenchAddin or %NULL
2448 *
2449 * Since: 3.32
2450 */
2451 IdeWorkbenchAddin *
ide_workbench_addin_find_by_module_name(IdeWorkbench * workbench,const gchar * module_name)2452 ide_workbench_addin_find_by_module_name (IdeWorkbench *workbench,
2453 const gchar *module_name)
2454 {
2455 PeasPluginInfo *plugin_info;
2456 PeasExtension *ret = NULL;
2457 PeasEngine *engine;
2458
2459 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
2460 g_return_val_if_fail (IDE_IS_WORKBENCH (workbench), NULL);
2461 g_return_val_if_fail (module_name != NULL, NULL);
2462
2463 if (workbench->addins == NULL)
2464 return NULL;
2465
2466 engine = peas_engine_get_default ();
2467
2468 if ((plugin_info = peas_engine_get_plugin_info (engine, module_name)))
2469 ret = peas_extension_set_get_extension (workbench->addins, plugin_info);
2470
2471 return IDE_WORKBENCH_ADDIN (ret);
2472 }
2473
2474 static void
ide_workbench_resolve_file_worker(IdeTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)2475 ide_workbench_resolve_file_worker (IdeTask *task,
2476 gpointer source_object,
2477 gpointer task_data,
2478 GCancellable *cancellable)
2479 {
2480 ResolveFile *rf = task_data;
2481 g_autofree gchar *basename = NULL;
2482
2483 g_assert (IDE_IS_TASK (task));
2484 g_assert (IDE_IS_WORKBENCH (source_object));
2485 g_assert (rf != NULL);
2486 g_assert (rf->roots != NULL);
2487 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
2488
2489 basename = g_path_get_basename (rf->path);
2490
2491 for (guint i = 0; i < rf->roots->len; i++)
2492 {
2493 GFile *root = g_ptr_array_index (rf->roots, i);
2494 g_autoptr(GFile) child = g_file_get_child (root, rf->path);
2495 g_autoptr(GPtrArray) found = NULL;
2496
2497 if (g_file_query_exists (child, cancellable))
2498 {
2499 ide_task_return_pointer (task, g_steal_pointer (&child), g_object_unref);
2500 return;
2501 }
2502
2503 found = ide_g_file_find_with_depth (root, basename, 0, cancellable);
2504 IDE_PTR_ARRAY_SET_FREE_FUNC (found, g_object_unref);
2505
2506 if (found != NULL && found->len > 0)
2507 {
2508 GFile *match = g_ptr_array_index (found, 0);
2509 ide_task_return_pointer (task, g_object_ref (match), g_object_unref);
2510 return;
2511 }
2512 }
2513
2514 ide_task_return_new_error (task,
2515 G_IO_ERROR,
2516 G_IO_ERROR_NOT_FOUND,
2517 "Failed to locate file %s",
2518 basename);
2519 }
2520
2521 /**
2522 * ide_workbench_resolve_file_async:
2523 * @self: a #IdeWorkbench
2524 * @filename: the filename to discover
2525 *
2526 * This function will try to locate a given file based on the filename,
2527 * possibly resolving it from a build directory, or source directory.
2528 *
2529 * If no file was discovered, some attempt will be made to locate a file
2530 * that matches appropriately.
2531 *
2532 * Since: 3.32
2533 */
2534 void
ide_workbench_resolve_file_async(IdeWorkbench * self,const gchar * filename,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)2535 ide_workbench_resolve_file_async (IdeWorkbench *self,
2536 const gchar *filename,
2537 GCancellable *cancellable,
2538 GAsyncReadyCallback callback,
2539 gpointer user_data)
2540 {
2541 g_autoptr(IdeTask) task = NULL;
2542 ResolveFile *rf;
2543
2544 IDE_ENTRY;
2545
2546 g_return_if_fail (IDE_IS_MAIN_THREAD ());
2547 g_return_if_fail (IDE_IS_WORKBENCH (self));
2548 g_return_if_fail (filename != NULL);
2549
2550 task = ide_task_new (self, cancellable, callback, user_data);
2551 ide_task_set_source_tag (task, ide_workbench_resolve_file_async);
2552
2553 rf = g_slice_new0 (ResolveFile);
2554 rf->roots = g_ptr_array_new_with_free_func (g_object_unref);
2555 rf->path = g_strdup (filename);
2556
2557 g_ptr_array_add (rf->roots, ide_context_ref_workdir (self->context));
2558
2559 if (ide_workbench_has_project (self))
2560 {
2561 IdeBuildManager *build_manager = ide_build_manager_from_context (self->context);
2562 IdePipeline *pipeline = ide_build_manager_get_pipeline (build_manager);
2563
2564 if (pipeline != NULL)
2565 {
2566 const gchar *builddir = ide_pipeline_get_builddir (pipeline);
2567
2568 g_ptr_array_add (rf->roots, g_file_new_for_path (builddir));
2569 }
2570 }
2571
2572 ide_task_set_task_data (task, rf, resolve_file_free);
2573 ide_task_run_in_thread (task, ide_workbench_resolve_file_worker);
2574
2575 IDE_EXIT;
2576 }
2577
2578 /**
2579 * ide_workbench_resolve_file_finish:
2580 * @self: a #IdeWorkbench
2581 * @result: a #GAsyncResult
2582 * @error: a location for a #GError
2583 *
2584 * Completes an asynchronous request to ide_workbench_resolve_file_async().
2585 *
2586 * Returns: (transfer full): a #GFile, or %NULL and @error is set
2587 *
2588 * Since: 3.32
2589 */
2590 GFile *
ide_workbench_resolve_file_finish(IdeWorkbench * self,GAsyncResult * result,GError ** error)2591 ide_workbench_resolve_file_finish (IdeWorkbench *self,
2592 GAsyncResult *result,
2593 GError **error)
2594 {
2595 GFile *ret;
2596
2597 IDE_ENTRY;
2598
2599 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
2600 g_return_val_if_fail (IDE_IS_WORKBENCH (self), NULL);
2601 g_return_val_if_fail (IDE_IS_TASK (result), NULL);
2602
2603 ret = ide_task_propagate_pointer (IDE_TASK (result), error);
2604
2605 IDE_RETURN (g_steal_pointer (&ret));
2606 }
2607