gem-graph-client/libide/gui/ide-workbench.c

2607 lines
78 KiB
C
Raw Permalink Normal View History

/* ide-workbench.c
*
* Copyright 2014-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "ide-workbench"
#include "config.h"
#include <glib/gi18n.h>
#include <libide-debugger.h>
#include <libide-threading.h>
#include <libpeas/peas.h>
#include "ide-build-private.h"
#include "ide-context-private.h"
#include "ide-foundry-init.h"
#include "ide-thread-private.h"
#include "ide-transfer-manager-private.h"
#include "ide-application.h"
#include "ide-command-manager.h"
#include "ide-gui-global.h"
#include "ide-gui-private.h"
#include "ide-primary-workspace.h"
#include "ide-workbench.h"
#include "ide-workbench-addin.h"
#include "ide-workspace.h"
/**
* SECTION:ide-workbench
* @title: IdeWorkbench
* @short_description: window group for all windows within a project
*
* The #IdeWorkbench is a #GtkWindowGroup containing the #IdeContext (root
* data-structure for a project) and all of the windows associated with the
* project.
*
* Usually, windows within the #IdeWorkbench are an #IdeWorkspace. They can
* react to changes in the #IdeContext or its descendants to represent the
* project and it's state.
*
* Since: 3.32
*/
struct _IdeWorkbench
{
GtkWindowGroup parent_instance;
/* MRU of workspaces, link embedded in workspace */
GQueue mru_queue;
/* Owned references */
PeasExtensionSet *addins;
GCancellable *cancellable;
IdeContext *context;
IdeBuildSystem *build_system;
IdeProjectInfo *project_info;
IdeVcs *vcs;
IdeVcsMonitor *vcs_monitor;
IdeSearchEngine *search_engine;
/* Various flags */
guint unloaded : 1;
};
typedef struct
{
GPtrArray *addins;
IdeWorkbenchAddin *preferred;
GFile *file;
gchar *hint;
gchar *content_type;
IdeBufferOpenFlags flags;
gint at_line;
gint at_line_offset;
} Open;
typedef struct
{
IdeProjectInfo *project_info;
GPtrArray *addins;
GType workspace_type;
gint64 present_time;
} LoadProject;
typedef struct
{
GPtrArray *roots;
gchar *path;
} ResolveFile;
enum {
PROP_0,
PROP_CONTEXT,
PROP_VCS,
N_PROPS
};
static void ide_workbench_action_close (IdeWorkbench *self,
GVariant *param);
static void ide_workbench_action_open (IdeWorkbench *self,
GVariant *param);
static void ide_workbench_action_dump_tasks (IdeWorkbench *self,
GVariant *param);
static void ide_workbench_action_object_tree (IdeWorkbench *self,
GVariant *param);
static void ide_workbench_action_inspector (IdeWorkbench *self,
GVariant *param);
static void ide_workbench_action_reload_all (IdeWorkbench *self,
GVariant *param);
DZL_DEFINE_ACTION_GROUP (IdeWorkbench, ide_workbench, {
{ "close", ide_workbench_action_close },
{ "open", ide_workbench_action_open },
{ "reload-files", ide_workbench_action_reload_all },
{ "-inspector", ide_workbench_action_inspector },
{ "-object-tree", ide_workbench_action_object_tree },
{ "-dump-tasks", ide_workbench_action_dump_tasks },
})
G_DEFINE_FINAL_TYPE_WITH_CODE (IdeWorkbench, ide_workbench, GTK_TYPE_WINDOW_GROUP,
G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP,
ide_workbench_init_action_group))
static GParamSpec *properties [N_PROPS];
static void
load_project_free (LoadProject *lp)
{
g_clear_object (&lp->project_info);
g_clear_pointer (&lp->addins, g_ptr_array_unref);
g_slice_free (LoadProject, lp);
}
static void
open_free (Open *o)
{
g_clear_pointer (&o->addins, g_ptr_array_unref);
g_clear_object (&o->preferred);
g_clear_object (&o->file);
g_clear_pointer (&o->hint, g_free);
g_clear_pointer (&o->content_type, g_free);
g_slice_free (Open, o);
}
static void
resolve_file_free (ResolveFile *rf)
{
g_clear_pointer (&rf->roots, g_ptr_array_unref);
g_clear_pointer (&rf->path, g_free);
g_slice_free (ResolveFile, rf);
}
static gboolean
ignore_error (GError *error)
{
return g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) ||
g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED);
}
/**
* ide_workbench_from_context:
* @context: an #IdeContext
*
* Helper to get the #IdeWorkbench for a given context.
*
* Returns: (transfer none) (nullable): an #IdeWorkbench or %NULL
*
* Since: 3.40
*/
IdeWorkbench *
ide_workbench_from_context (IdeContext *context)
{
g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
return IDE_WORKBENCH (g_object_get_data (G_OBJECT (context), "WORKBENCH"));
}
static void
ide_workbench_set_context (IdeWorkbench *self,
IdeContext *context)
{
g_autoptr(IdeContext) new_context = NULL;
g_autoptr(IdeBufferManager) bufmgr = NULL;
IdeBuildSystem *build_system;
g_return_if_fail (IDE_IS_WORKBENCH (self));
g_return_if_fail (!context || IDE_IS_CONTEXT (context));
if (context == NULL)
context = new_context = ide_context_new ();
/* backpointer for the workbench */
g_object_set_data (G_OBJECT (context), "WORKBENCH", self);
g_set_object (&self->context, context);
/* Make sure we have access to buffer manager early */
bufmgr = ide_object_ensure_child_typed (IDE_OBJECT (context), IDE_TYPE_BUFFER_MANAGER);
/* And use a fallback build system if one is not already available */
if ((build_system = ide_context_peek_child_typed (context, IDE_TYPE_BUILD_SYSTEM)))
self->build_system = g_object_ref (build_system);
else
self->build_system = ide_object_ensure_child_typed (IDE_OBJECT (context), IDE_TYPE_FALLBACK_BUILD_SYSTEM);
}
static void
ide_workbench_addin_added_workspace_cb (IdeWorkspace *workspace,
IdeWorkbenchAddin *addin)
{
g_assert (IDE_IS_WORKSPACE (workspace));
g_assert (IDE_IS_WORKBENCH_ADDIN (addin));
ide_workbench_addin_workspace_added (addin, workspace);
}
static void
ide_workbench_addin_removed_workspace_cb (IdeWorkspace *workspace,
IdeWorkbenchAddin *addin)
{
g_assert (IDE_IS_WORKSPACE (workspace));
g_assert (IDE_IS_WORKBENCH_ADDIN (addin));
ide_workbench_addin_workspace_removed (addin, workspace);
}
static void
ide_workbench_addin_added_cb (PeasExtensionSet *set,
PeasPluginInfo *plugin_info,
PeasExtension *exten,
gpointer user_data)
{
IdeWorkbench *self = user_data;
IdeWorkbenchAddin *addin = (IdeWorkbenchAddin *)exten;
g_assert (PEAS_IS_EXTENSION_SET (set));
g_assert (plugin_info != NULL);
g_assert (IDE_IS_WORKBENCH_ADDIN (addin));
g_assert (IDE_IS_WORKBENCH (self));
ide_workbench_addin_load (addin, self);
/* Notify of the VCS system up-front */
if (self->vcs != NULL)
ide_workbench_addin_vcs_changed (addin, self->vcs);
/*
* If we already loaded a project, then give the plugin a
* chance to handle that, even if it is delayed a bit.
*/
if (self->project_info != NULL)
ide_workbench_addin_load_project_async (addin, self->project_info, NULL, NULL, NULL);
ide_workbench_foreach_workspace (self,
(GtkCallback)ide_workbench_addin_added_workspace_cb,
addin);
}
static void
ide_workbench_addin_removed_cb (PeasExtensionSet *set,
PeasPluginInfo *plugin_info,
PeasExtension *exten,
gpointer user_data)
{
IdeWorkbench *self = user_data;
IdeWorkbenchAddin *addin = (IdeWorkbenchAddin *)exten;
g_assert (PEAS_IS_EXTENSION_SET (set));
g_assert (plugin_info != NULL);
g_assert (IDE_IS_WORKBENCH_ADDIN (addin));
g_assert (IDE_IS_WORKBENCH (self));
/* Notify of workspace removals so addins don't need to manually
* track them for cleanup.
*/
ide_workbench_foreach_workspace (self,
(GtkCallback)ide_workbench_addin_removed_workspace_cb,
addin);
ide_workbench_addin_unload (addin, self);
}
static void
ide_workbench_notify_context_title (IdeWorkbench *self,
GParamSpec *pspec,
IdeContext *context)
{
g_autofree gchar *formatted = NULL;
g_autofree gchar *title = NULL;
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_WORKBENCH (self));
g_assert (IDE_IS_CONTEXT (context));
title = ide_context_dup_title (context);
formatted = g_strdup_printf (_("Builder — %s"), title);
ide_workbench_foreach_workspace (self,
(GtkCallback)gtk_window_set_title,
formatted);
}
static void
ide_workbench_notify_context_workdir (IdeWorkbench *self,
GParamSpec *pspec,
IdeContext *context)
{
g_autoptr(GFile) workdir = NULL;
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_WORKBENCH (self));
g_assert (IDE_IS_CONTEXT (context));
workdir = ide_context_ref_workdir (context);
ide_vcs_monitor_set_root (self->vcs_monitor, workdir);
}
static void
ide_workbench_constructed (GObject *object)
{
IdeWorkbench *self = (IdeWorkbench *)object;
g_assert (IDE_IS_WORKBENCH (self));
if (self->context == NULL)
self->context = ide_context_new ();
g_signal_connect_object (self->context,
"notify::title",
G_CALLBACK (ide_workbench_notify_context_title),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (self->context,
"notify::workdir",
G_CALLBACK (ide_workbench_notify_context_workdir),
self,
G_CONNECT_SWAPPED);
G_OBJECT_CLASS (ide_workbench_parent_class)->constructed (object);
self->vcs_monitor = g_object_new (IDE_TYPE_VCS_MONITOR,
"parent", self->context,
NULL);
self->addins = peas_extension_set_new (peas_engine_get_default (),
IDE_TYPE_WORKBENCH_ADDIN,
NULL);
g_signal_connect (self->addins,
"extension-added",
G_CALLBACK (ide_workbench_addin_added_cb),
self);
g_signal_connect (self->addins,
"extension-removed",
G_CALLBACK (ide_workbench_addin_removed_cb),
self);
peas_extension_set_foreach (self->addins,
ide_workbench_addin_added_cb,
self);
/* Load command providers (which may register shortcuts) */
(void)ide_command_manager_from_context (self->context);
}
static void
ide_workbench_finalize (GObject *object)
{
IdeWorkbench *self = (IdeWorkbench *)object;
g_assert (IDE_IS_MAIN_THREAD ());
if (self->context != NULL)
g_object_set_data (G_OBJECT (self->context), "WORKBENCH", NULL);
g_clear_object (&self->build_system);
g_clear_object (&self->vcs);
g_clear_object (&self->search_engine);
g_clear_object (&self->project_info);
g_clear_object (&self->cancellable);
g_clear_object (&self->context);
G_OBJECT_CLASS (ide_workbench_parent_class)->finalize (object);
}
static void
ide_workbench_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
IdeWorkbench *self = IDE_WORKBENCH (object);
g_assert (IDE_IS_MAIN_THREAD ());
switch (prop_id)
{
case PROP_CONTEXT:
g_value_set_object (value, ide_workbench_get_context (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
ide_workbench_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
IdeWorkbench *self = IDE_WORKBENCH (object);
g_assert (IDE_IS_MAIN_THREAD ());
switch (prop_id)
{
case PROP_CONTEXT:
ide_workbench_set_context (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
ide_workbench_class_init (IdeWorkbenchClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->constructed = ide_workbench_constructed;
object_class->finalize = ide_workbench_finalize;
object_class->get_property = ide_workbench_get_property;
object_class->set_property = ide_workbench_set_property;
/**
* IdeWorkbench:context:
*
* The "context" property is the #IdeContext for the project.
*
* The #IdeContext is the root #IdeObject used in the tree of
* objects representing the project and the workings of the IDE.
*
* Since: 3.32
*/
properties [PROP_CONTEXT] =
g_param_spec_object ("context",
"Context",
"The IdeContext for the workbench",
IDE_TYPE_CONTEXT,
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
/**
* IdeWorkbench:vcs:
*
* The "vcs" property contains an #IdeVcs that represents the version control
* system that is currently loaded for the project.
*
* The #IdeVcs is registered by an #IdeWorkbenchAddin when loading a project.
*
* Since: 3.32
*/
properties [PROP_VCS] =
g_param_spec_object ("vcs",
"Vcs",
"The version control system, if any",
IDE_TYPE_VCS,
(G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
ide_workbench_init (IdeWorkbench *self)
{
}
static void
collect_addins_cb (PeasExtensionSet *set,
PeasPluginInfo *plugin_info,
PeasExtension *exten,
gpointer user_data)
{
GPtrArray *ar = user_data;
g_ptr_array_add (ar, g_object_ref (exten));
}
static GPtrArray *
ide_workbench_collect_addins (IdeWorkbench *self)
{
g_autoptr(GPtrArray) ar = NULL;
g_assert (IDE_IS_WORKBENCH (self));
ar = g_ptr_array_new_with_free_func (g_object_unref);
if (self->addins != NULL)
peas_extension_set_foreach (self->addins, collect_addins_cb, ar);
return g_steal_pointer (&ar);
}
static IdeWorkbenchAddin *
ide_workbench_find_addin (IdeWorkbench *self,
const gchar *hint)
{
PeasEngine *engine;
PeasPluginInfo *plugin_info;
PeasExtension *exten = NULL;
g_return_val_if_fail (IDE_IS_WORKBENCH (self), NULL);
g_return_val_if_fail (hint != NULL, NULL);
engine = peas_engine_get_default ();
if ((plugin_info = peas_engine_get_plugin_info (engine, hint)))
exten = peas_extension_set_get_extension (self->addins, plugin_info);
return exten ? g_object_ref (IDE_WORKBENCH_ADDIN (exten)) : NULL;
}
/**
* ide_workbench_new:
*
* Creates a new #IdeWorkbench.
*
* This does not create any windows, you'll need to request that a workspace
* be created based on the kind of workspace you want to display to the user.
*
* Returns: an #IdeWorkbench
*
* Since: 3.32
*/
IdeWorkbench *
ide_workbench_new (void)
{
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
return g_object_new (IDE_TYPE_WORKBENCH, NULL);
}
/**
* ide_workbench_new_for_context:
*
* Creates a new #IdeWorkbench using @context for the #IdeWorkbench:context.
*
* Returns: (transfer full): an #IdeWorkbench
*
* Since: 3.32
*/
IdeWorkbench *
ide_workbench_new_for_context (IdeContext *context)
{
g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
return g_object_new (IDE_TYPE_CONTEXT,
"visible", TRUE,
NULL);
}
/**
* ide_workbench_get_context:
* @self: an #IdeWorkbench
*
* Gets the #IdeContext for the workbench.
*
* Returns: (transfer none): an #IdeContext
*
* Since: 3.32
*/
IdeContext *
ide_workbench_get_context (IdeWorkbench *self)
{
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
g_return_val_if_fail (IDE_IS_WORKBENCH (self), NULL);
return self->context;
}
/**
* ide_workbench_from_widget:
* @widget: a #GtkWidget
*
* Finds the #IdeWorkbench associated with a widget.
*
* Returns: (nullable) (transfer none): an #IdeWorkbench or %NULL
*
* Since: 3.32
*/
IdeWorkbench *
ide_workbench_from_widget (GtkWidget *widget)
{
GtkWindowGroup *group;
GtkWidget *toplevel;
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
/*
* The workbench is a window group, and the workspaces belong to us. So we
* just need to get the toplevel window group property, and cast.
*/
if ((toplevel = gtk_widget_get_toplevel (widget)) &&
GTK_IS_WINDOW (toplevel) &&
(group = gtk_window_get_group (GTK_WINDOW (toplevel))) &&
IDE_IS_WORKBENCH (group))
return IDE_WORKBENCH (group);
return NULL;
}
/**
* ide_workbench_foreach_workspace:
* @self: an #IdeWorkbench
* @callback: (scope call): a #GtkCallback to call for each #IdeWorkspace
* @user_data: user data for @callback
*
* Iterates the available workspaces in the workbench. Workspaces are iterated
* in most-recently-used order.
*
* Since: 3.32
*/
void
ide_workbench_foreach_workspace (IdeWorkbench *self,
GtkCallback callback,
gpointer user_data)
{
GList *copy;
g_return_if_fail (IDE_IS_MAIN_THREAD ());
g_return_if_fail (IDE_IS_WORKBENCH (self));
g_return_if_fail (callback != NULL);
/* Copy for re-entrancy safety */
copy = g_list_copy (self->mru_queue.head);
for (const GList *iter = copy; iter; iter = iter->next)
{
IdeWorkspace *workspace = iter->data;
g_assert (IDE_IS_WORKSPACE (workspace));
callback (GTK_WIDGET (workspace), user_data);
}
g_list_free (copy);
}
/**
* ide_workbench_foreach_page:
* @self: a #IdeWorkbench
* @callback: (scope call): a callback to execute for each page
* @user_data: closure data for @callback
*
* Calls @callback for every page loaded in the workbench, by iterating
* workspaces in order of most-recently-used.
*
* Since: 3.32
*/
void
ide_workbench_foreach_page (IdeWorkbench *self,
GtkCallback callback,
gpointer user_data)
{
GList *copy;
g_return_if_fail (IDE_IS_WORKBENCH (self));
g_return_if_fail (callback != NULL);
/* Make a copy to be safe against auto-cleanup removals */
copy = g_list_copy (self->mru_queue.head);
for (const GList *iter = copy; iter; iter = iter->next)
{
IdeWorkspace *workspace = iter->data;
g_assert (IDE_IS_WORKSPACE (workspace));
ide_workspace_foreach_page (workspace, callback, user_data);
}
g_list_free (copy);
}
static void
ide_workbench_workspace_has_toplevel_focus_cb (IdeWorkbench *self,
GParamSpec *pspec,
IdeWorkspace *workspace)
{
g_assert (IDE_IS_WORKBENCH (self));
g_assert (IDE_IS_WORKSPACE (workspace));
g_assert (gtk_window_get_group (GTK_WINDOW (workspace)) == GTK_WINDOW_GROUP (self));
if (gtk_window_has_toplevel_focus (GTK_WINDOW (workspace)))
{
GList *mru_link = _ide_workspace_get_mru_link (workspace);
g_queue_unlink (&self->mru_queue, mru_link);
g_assert (mru_link->prev == NULL);
g_assert (mru_link->next == NULL);
g_assert (mru_link->data == (gpointer)workspace);
g_queue_push_head_link (&self->mru_queue, mru_link);
}
}
static void
insert_action_groups_foreach_cb (IdeWorkspace *workspace,
gpointer user_data)
{
IdeWorkbench *self = user_data;
struct {
const gchar *name;
GType child_type;
} groups[] = {
{ "config-manager", IDE_TYPE_CONFIG_MANAGER },
{ "build-manager", IDE_TYPE_BUILD_MANAGER },
{ "device-manager", IDE_TYPE_DEVICE_MANAGER },
{ "run-manager", IDE_TYPE_RUN_MANAGER },
{ "test-manager", IDE_TYPE_TEST_MANAGER },
};
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_WORKBENCH (self));
g_assert (IDE_IS_WORKSPACE (workspace));
for (guint i = 0; i < G_N_ELEMENTS (groups); i++)
{
IdeObject *child;
if ((child = ide_context_peek_child_typed (self->context, groups[i].child_type)))
gtk_widget_insert_action_group (GTK_WIDGET (workspace),
groups[i].name,
G_ACTION_GROUP (child));
}
}
/**
* ide_workbench_add_workspace:
* @self: an #IdeWorkbench
* @workspace: an #IdeWorkspace
*
* Adds @workspace to @workbench.
*
* Since: 3.32
*/
void
ide_workbench_add_workspace (IdeWorkbench *self,
IdeWorkspace *workspace)
{
g_autoptr(GPtrArray) addins = NULL;
IdeCommandManager *command_manager;
GList *mru_link;
g_return_if_fail (IDE_IS_MAIN_THREAD ());
g_return_if_fail (IDE_IS_WORKBENCH (self));
g_return_if_fail (IDE_IS_WORKSPACE (workspace));
/* Now add the window to the workspace (which takes no reference, as the
* window will take a reference back to us.
*/
if (gtk_window_get_group (GTK_WINDOW (workspace)) != GTK_WINDOW_GROUP (self))
gtk_window_group_add_window (GTK_WINDOW_GROUP (self), GTK_WINDOW (workspace));
g_assert (gtk_window_has_group (GTK_WINDOW (workspace)));
g_assert (gtk_window_get_group (GTK_WINDOW (workspace)) == GTK_WINDOW_GROUP (self));
/* Now place the workspace into our MRU tracking */
mru_link = _ide_workspace_get_mru_link (workspace);
/* New workspaces are expected to be displayed right away, so we can
* just push the window onto the head.
*/
g_queue_push_head_link (&self->mru_queue, mru_link);
/* Update the context for the workspace, even if we're not loaded,
* this IdeContext will be updated later.
*/
_ide_workspace_set_context (workspace, self->context);
/* This causes the workspace to get an additional reference to the group
* (which already happens from GtkWindow:group), but IdeWorkspace will
* remove itself in IdeWorkspace.destroy.
*/
gtk_widget_insert_action_group (GTK_WIDGET (workspace),
"workbench",
G_ACTION_GROUP (self));
/* Give the workspace access to all the action groups of the context that
* might be useful for them to access (debug-manager, run-manager, etc).
*/
if (self->project_info != NULL)
insert_action_groups_foreach_cb (workspace, self);
/* Track toplevel focus changes to maintain a most-recently-used queue. */
g_signal_connect_object (workspace,
"notify::has-toplevel-focus",
G_CALLBACK (ide_workbench_workspace_has_toplevel_focus_cb),
self,
G_CONNECT_SWAPPED);
/* Give access to transfer-manager */
gtk_widget_insert_action_group (GTK_WIDGET (workspace),
"transfer-manager",
_ide_transfer_manager_get_actions (NULL));
/* Notify all the addins about the new workspace. */
if ((addins = ide_workbench_collect_addins (self)))
{
for (guint i = 0; i < addins->len; i++)
{
IdeWorkbenchAddin *addin = g_ptr_array_index (addins, i);
ide_workbench_addin_workspace_added (addin, workspace);
}
}
if (!gtk_window_get_title (GTK_WINDOW (workspace)))
{
g_autofree gchar *title = NULL;
g_autofree gchar *formatted = NULL;
title = ide_context_dup_title (self->context);
formatted = g_strdup_printf (_("Builder — %s"), title);
gtk_window_set_title (GTK_WINDOW (workspace), formatted);
}
/* Load shortcuts for commands */
command_manager = ide_command_manager_from_context (self->context);
_ide_command_manager_init_shortcuts (command_manager, workspace);
}
/**
* ide_workbench_remove_workspace:
* @self: an #IdeWorkbench
* @workspace: an #IdeWorkspace
*
* Removes @workspace from @workbench.
*
* Since: 3.32
*/
void
ide_workbench_remove_workspace (IdeWorkbench *self,
IdeWorkspace *workspace)
{
g_autoptr(GPtrArray) addins = NULL;
IdeCommandManager *command_manager;
GList *list;
GList *mru_link;
guint count = 0;
g_return_if_fail (IDE_IS_MAIN_THREAD ());
g_return_if_fail (IDE_IS_WORKBENCH (self));
g_return_if_fail (IDE_IS_WORKSPACE (workspace));
/* Stop tracking MRU changes */
mru_link = _ide_workspace_get_mru_link (workspace);
g_queue_unlink (&self->mru_queue, mru_link);
g_signal_handlers_disconnect_by_func (workspace,
G_CALLBACK (ide_workbench_workspace_has_toplevel_focus_cb),
self);
/* Remove any shortcuts that were registered by command providers */
command_manager = ide_command_manager_from_context (self->context);
_ide_command_manager_unload_shortcuts (command_manager, workspace);
/* Notify all the addins about losing the workspace. */
if ((addins = ide_workbench_collect_addins (self)))
{
for (guint i = 0; i < addins->len; i++)
{
IdeWorkbenchAddin *addin = g_ptr_array_index (addins, i);
ide_workbench_addin_workspace_removed (addin, workspace);
}
}
/* Clear our action group (which drops an additional back-reference) */
gtk_widget_insert_action_group (GTK_WIDGET (workspace), "workbench", NULL);
/* Only cleanup the group if it hasn't already been removed */
if (gtk_window_has_group (GTK_WINDOW (workspace)))
gtk_window_group_remove_window (GTK_WINDOW_GROUP (self), GTK_WINDOW (workspace));
/*
* If this is our last workspace being closed, then we want to
* try to cleanup the workbench and shut things down.
*/
list = gtk_window_group_list_windows (GTK_WINDOW_GROUP (self));
for (const GList *iter = list; iter; iter = iter->next)
{
GtkWindow *window = iter->data;
if (IDE_IS_WORKSPACE (window) && workspace != IDE_WORKSPACE (window))
count++;
}
g_list_free (list);
/*
* If there are no more workspaces left, then we will want to also
* unload the workbench opportunistically, so that the application
* can exit cleanly.
*/
if (count == 0 && self->unloaded == FALSE)
ide_workbench_unload_async (self, NULL, NULL, NULL);
}
/**
* ide_workbench_focus_workspace:
* @self: an #IdeWorkbench
* @workspace: an #IdeWorkspace
*
* Requests that @workspace be raised in the windows of @self, and
* displayed to the user.
*
* Since: 3.32
*/
void
ide_workbench_focus_workspace (IdeWorkbench *self,
IdeWorkspace *workspace)
{
g_return_if_fail (IDE_IS_MAIN_THREAD ());
g_return_if_fail (IDE_IS_WORKBENCH (self));
g_return_if_fail (IDE_IS_WORKSPACE (workspace));
ide_gtk_window_present (GTK_WINDOW (workspace));
}
static void
ide_workbench_project_loaded_foreach_cb (PeasExtensionSet *set,
PeasPluginInfo *plugin_info,
PeasExtension *exten,
gpointer user_data)
{
IdeWorkbenchAddin *addin = (IdeWorkbenchAddin *)exten;
IdeWorkbench *self = user_data;
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (PEAS_IS_EXTENSION_SET (set));
g_assert (IDE_IS_WORKBENCH_ADDIN (addin));
g_assert (IDE_IS_WORKBENCH (self));
g_assert (IDE_IS_PROJECT_INFO (self->project_info));
ide_workbench_addin_project_loaded (addin, self->project_info);
}
static void
ide_workbench_load_project_completed (IdeWorkbench *self,
IdeTask *task)
{
IdeBuildManager *build_manager;
LoadProject *lp;
g_assert (IDE_IS_WORKBENCH (self));
g_assert (IDE_IS_TASK (task));
lp = ide_task_get_task_data (task);
g_assert (lp != NULL);
g_assert (lp->addins != NULL);
g_assert (lp->addins->len == 0);
/* If we did not get a VCS as part of the loading process, set the
* fallback VCS implementation.
*/
if (self->vcs == NULL)
{
g_autoptr(GFile) workdir = ide_context_ref_workdir (self->context);
g_autoptr(IdeDirectoryVcs) vcs = ide_directory_vcs_new (workdir);
ide_workbench_set_vcs (self, IDE_VCS (vcs));
}
/* Create the search engine up-front */
if (self->search_engine == NULL)
self->search_engine = ide_object_ensure_child_typed (IDE_OBJECT (self->context),
IDE_TYPE_SEARCH_ENGINE);
if (lp->workspace_type != G_TYPE_INVALID)
{
IdeWorkspace *workspace;
workspace = g_object_new (lp->workspace_type,
"application", IDE_APPLICATION_DEFAULT,
NULL);
ide_workbench_add_workspace (self, IDE_WORKSPACE (workspace));
gtk_window_present_with_time (GTK_WINDOW (workspace), lp->present_time);
}
/* Give workspaces access to the various GActionGroups */
ide_workbench_foreach_workspace (self,
(GtkCallback)insert_action_groups_foreach_cb,
self);
/* Notify addins that projects have loaded */
peas_extension_set_foreach (self->addins,
ide_workbench_project_loaded_foreach_cb,
self);
/* Now that we have a workspace window for the project, we can allow
* the build manager to start.
*/
build_manager = ide_build_manager_from_context (self->context);
_ide_build_manager_start (build_manager);
ide_task_return_boolean (task, TRUE);
}
static void
ide_workbench_load_project_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
IdeWorkbenchAddin *addin = (IdeWorkbenchAddin *)object;
g_autoptr(IdeTask) task = user_data;
g_autoptr(GError) error = NULL;
IdeWorkbench *self;
LoadProject *lp;
g_assert (IDE_IS_WORKBENCH_ADDIN (addin));
g_assert (G_IS_ASYNC_RESULT (result));
g_assert (IDE_IS_TASK (task));
self = ide_task_get_source_object (task);
lp = ide_task_get_task_data (task);
g_assert (IDE_IS_WORKBENCH (self));
g_assert (lp != NULL);
g_assert (IDE_IS_PROJECT_INFO (lp->project_info));
g_assert (lp->addins != NULL);
g_assert (lp->addins->len > 0);
if (!ide_workbench_addin_load_project_finish (addin, result, &error))
{
if (!ignore_error (error))
g_warning ("%s addin failed to load project: %s",
G_OBJECT_TYPE_NAME (addin), error->message);
}
g_ptr_array_remove (lp->addins, addin);
if (lp->addins->len == 0)
ide_workbench_load_project_completed (self, task);
}
static void
ide_workbench_init_foundry_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
g_autoptr(IdeTask) task = user_data;
g_autoptr(GError) error = NULL;
IdeWorkbench *self;
GCancellable *cancellable;
LoadProject *lp;
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (G_IS_ASYNC_RESULT (result));
g_assert (IDE_IS_TASK (task));
if (!_ide_foundry_init_finish (result, &error))
g_critical ("Failed to initialize foundry: %s", error->message);
cancellable = ide_task_get_cancellable (task);
self = ide_task_get_source_object (task);
lp = ide_task_get_task_data (task);
g_assert (IDE_IS_WORKBENCH (self));
g_assert (lp != NULL);
g_assert (lp->addins != NULL);
g_assert (IDE_IS_PROJECT_INFO (lp->project_info));
/* Now, we need to notify all of the workbench addins that we're
* opening the project. Once they have all completed, we'll create the
* new workspace window and attach it. That saves us the work of
* rendering various frames of the during the intensive load process.
*/
for (guint i = 0; i < lp->addins->len; i++)
{
IdeWorkbenchAddin *addin = g_ptr_array_index (lp->addins, i);
ide_workbench_addin_load_project_async (addin,
lp->project_info,
cancellable,
ide_workbench_load_project_cb,
g_object_ref (task));
}
if (lp->addins->len == 0)
ide_workbench_load_project_completed (self, task);
}
/**
* ide_workbench_load_project_async:
* @self: a #IdeWorkbench
* @project_info: an #IdeProjectInfo describing the project to open
* @cancellable: (nullable): a #GCancellable or %NULL
* @callback: (nullable): a #GAsyncReadyCallback to execute upon completion
* @user_data: user data for @callback
*
* Requests that a project be opened in the workbench.
*
* @project_info should contain enough information to discover and load the
* project. Depending on the various fields of the #IdeProjectInfo,
* different plugins may become active as part of loading the project.
*
* Note that this may only be called once for an #IdeWorkbench. If you need
* to open a second project, you need to create and register a second
* workbench first, and then open using that secondary workbench.
*
* @callback should call ide_workbench_load_project_finish() to obtain the
* result of the open request.
*
* Since: 3.32
*/
void
ide_workbench_load_project_async (IdeWorkbench *self,
IdeProjectInfo *project_info,
GType workspace_type,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(IdeTask) task = NULL;
g_autoptr(GFile) parent = NULL;
g_autofree gchar *name = NULL;
const gchar *project_id;
LoadProject *lp;
GFile *directory;
GFile *file;
IDE_ENTRY;
g_return_if_fail (IDE_IS_MAIN_THREAD ());
g_return_if_fail (IDE_IS_WORKBENCH (self));
g_return_if_fail (IDE_IS_PROJECT_INFO (project_info));
g_return_if_fail (workspace_type != IDE_TYPE_WORKSPACE);
g_return_if_fail (workspace_type == G_TYPE_INVALID ||
g_type_is_a (workspace_type, IDE_TYPE_WORKSPACE));
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
g_return_if_fail (self->unloaded == FALSE);
task = ide_task_new (self, cancellable, callback, user_data);
ide_task_set_source_tag (task, ide_workbench_load_project_async);
if (self->project_info != NULL)
{
ide_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"Cannot load project, a project is already loaded");
IDE_EXIT;
}
_ide_context_set_has_project (self->context);
g_set_object (&self->project_info, project_info);
/* Update context project-id based on project-info */
if ((project_id = ide_project_info_get_id (project_info)))
{
g_autofree gchar *generated = ide_create_project_id (project_id);
ide_context_set_project_id (self->context, generated);
}
if (!ide_project_info_get_directory (project_info) &&
!ide_project_info_get_file (project_info))
{
ide_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_NOT_FOUND,
"No file or directory provided to load as project");
IDE_EXIT;
}
/* Fallback to using directory as file if necessary */
if (!(file = ide_project_info_get_file (project_info)))
{
file = ide_project_info_get_directory (project_info);
g_assert (G_IS_FILE (file));
ide_project_info_set_file (project_info, file);
}
/*
* Track the directory root based on project info. If we didn't get a
* directory set, then take the parent of the project file.
*/
if ((directory = ide_project_info_get_directory (project_info)))
{
ide_context_set_workdir (self->context, directory);
}
else
{
if (g_file_query_file_type (file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) == G_FILE_TYPE_DIRECTORY)
{
ide_context_set_workdir (self->context, file);
directory = file;
}
else
{
ide_context_set_workdir (self->context, (parent = g_file_get_parent (file)));
directory = parent;
}
ide_project_info_set_directory (project_info, directory);
}
g_assert (G_IS_FILE (directory));
name = g_file_get_basename (directory);
ide_context_set_title (self->context, name);
{
GFile *pdir = ide_project_info_get_directory (project_info);
GFile *pfile = ide_project_info_get_file (project_info);
const gchar *pident = ide_project_info_get_id (project_info);
const gchar *pname = ide_project_info_get_name (project_info);
/* Log some information to help track down project loading issues. */
g_debug ("Loading project");
g_debug (" id = %s", pname);
g_debug (" name = %s", pident);
g_debug (" dir = %s", g_file_peek_path (pdir));
g_debug (" file = %s", g_file_peek_path (pfile));
}
/* If there has not been a project name set, make the default matching
* the directory name. A plugin may update the name with more information
* based on .doap files, etc.
*/
if (!ide_project_info_get_name (project_info))
ide_project_info_set_name (project_info, name);
/* Setup some information we're going to need later on when loading the
* individual workbench addins (and then creating the workspace).
*/
lp = g_slice_new0 (LoadProject);
lp->project_info = g_object_ref (project_info);
/* HACK: Workaround for lack of last event time */
lp->present_time = g_get_monotonic_time () / 1000L;
lp->addins = ide_workbench_collect_addins (self);
lp->workspace_type = workspace_type;
ide_task_set_task_data (task, lp, load_project_free);
/*
* Before we load any addins, we want to register the Foundry subsystems
* such as the device manager, diagnostics engine, configurations, etc.
* This makes sure that we have some basics setup before addins load.
*/
_ide_foundry_init_async (self->context,
cancellable,
ide_workbench_init_foundry_cb,
g_steal_pointer (&task));
IDE_EXIT;
}
/**
* ide_workbench_load_project_finish:
* @self: a #IdeWorkbench
*
* Completes an asynchronous request to open a project using
* ide_workbench_load_project_async().
*
* Returns: %TRUE if the project was successfully opened; otherwise %FALSE
* and @error is set.
*
* Since: 3.32
*/
gboolean
ide_workbench_load_project_finish (IdeWorkbench *self,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
g_return_val_if_fail (IDE_IS_WORKBENCH (self), FALSE);
g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
return ide_task_propagate_boolean (IDE_TASK (result), error);
}
static void
print_object_tree (IdeObject *object,
gpointer depthptr)
{
gint depth = GPOINTER_TO_INT (depthptr);
g_autofree gchar *space = g_strnfill (depth * 2, ' ');
g_autofree gchar *info = ide_object_repr (object);
g_print ("%s%s\n", space, info);
ide_object_foreach (object,
(GFunc)print_object_tree,
GINT_TO_POINTER (depth + 1));
}
static void
ide_workbench_action_object_tree (IdeWorkbench *self,
GVariant *param)
{
g_assert (IDE_IS_WORKBENCH (self));
print_object_tree (IDE_OBJECT (self->context), NULL);
}
static void
ide_workbench_action_dump_tasks (IdeWorkbench *self,
GVariant *param)
{
g_assert (IDE_IS_WORKBENCH (self));
_ide_dump_tasks ();
}
static void
ide_workbench_action_inspector (IdeWorkbench *self,
GVariant *param)
{
gtk_window_set_interactive_debugging (TRUE);
}
static void
ide_workbench_action_close_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
IdeWorkbench *self = (IdeWorkbench *)object;
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_WORKBENCH (self));
g_assert (G_IS_ASYNC_RESULT (result));
g_assert (user_data == NULL);
if (ide_workbench_unload_finish (self, result, NULL))
{
IdeApplication *app = IDE_APPLICATION_DEFAULT;
GtkWindow *active;
if (!(active = gtk_application_get_active_window (GTK_APPLICATION (app))))
g_application_activate (G_APPLICATION (app));
else
ide_gtk_window_present (active);
}
}
static void
ide_workbench_action_close (IdeWorkbench *self,
GVariant *param)
{
g_assert (IDE_IS_WORKBENCH (self));
g_assert (param == NULL);
if (self->unloaded == FALSE)
ide_workbench_unload_async (self,
NULL,
ide_workbench_action_close_cb,
NULL);
}
static void
ide_workbench_action_reload_all (IdeWorkbench *self,
GVariant *param)
{
IdeBufferManager *bufmgr;
IdeContext *context;
g_assert (IDE_IS_WORKBENCH (self));
g_assert (param == NULL);
context = ide_workbench_get_context (self);
bufmgr = ide_buffer_manager_from_context (context);
ide_buffer_manager_reload_all_async (bufmgr, NULL, NULL, NULL);
}
static void
ide_workbench_action_open (IdeWorkbench *self,
GVariant *param)
{
GtkFileChooserNative *chooser;
IdeWorkspace *workspace;
gint ret;
g_assert (IDE_IS_WORKBENCH (self));
g_assert (param == NULL);
workspace = ide_workbench_get_current_workspace (self);
chooser = gtk_file_chooser_native_new (_("Open File…"),
GTK_WINDOW (workspace),
GTK_FILE_CHOOSER_ACTION_OPEN,
_("_Open"),
_("_Cancel"));
gtk_native_dialog_set_modal (GTK_NATIVE_DIALOG (chooser), FALSE);
gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (chooser), FALSE);
gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (chooser), TRUE);
ret = gtk_native_dialog_run (GTK_NATIVE_DIALOG (chooser));
if (ret == GTK_RESPONSE_ACCEPT)
{
g_autoslist(GFile) files = gtk_file_chooser_get_files (GTK_FILE_CHOOSER (chooser));
for (const GSList *iter = files; iter; iter = iter->next)
{
GFile *file = iter->data;
g_assert (G_IS_FILE (file));
ide_workbench_open_async (self, file, NULL, 0, NULL, NULL, NULL);
}
}
gtk_native_dialog_destroy (GTK_NATIVE_DIALOG (chooser));
}
/**
* ide_workbench_get_search_engine:
* @self: a #IdeWorkbench
*
* Gets the search engine for the workbench, if any.
*
* Returns: (transfer none): an #IdeSearchEngine
*
* Since: 3.32
*/
IdeSearchEngine *
ide_workbench_get_search_engine (IdeWorkbench *self)
{
g_return_val_if_fail (IDE_IS_WORKBENCH (self), NULL);
g_return_val_if_fail (self->context != NULL, NULL);
if (self->search_engine == NULL)
self->search_engine = ide_object_ensure_child_typed (IDE_OBJECT (self->context),
IDE_TYPE_SEARCH_ENGINE);
return self->search_engine;
}
/**
* ide_workbench_get_project_info:
* @self: a #IdeWorkbench
*
* Gets the #IdeProjectInfo for the workbench, if a project has been or is
* currently, loading.
*
* Returns: (transfer none) (nullable): an #IdeProjectInfo or %NULL
*
* Since: 3.32
*/
IdeProjectInfo *
ide_workbench_get_project_info (IdeWorkbench *self)
{
g_return_val_if_fail (IDE_IS_WORKBENCH (self), NULL);
return self->project_info;
}
static void
ide_workbench_unload_foundry_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
g_autoptr(IdeTask) task = user_data;
g_autoptr(GError) error = NULL;
IdeWorkbench *self;
g_assert (G_IS_ASYNC_RESULT (result));
g_assert (IDE_IS_TASK (task));
self = ide_task_get_source_object (task);
if (!_ide_foundry_unload_finish (result, &error))
ide_task_return_error (task, g_steal_pointer (&error));
else
ide_task_return_boolean (task, TRUE);
if (self->context != NULL)
{
ide_object_destroy (IDE_OBJECT (self->context));
g_clear_object (&self->context);
}
}
static void
ide_workbench_unload_project_completed (IdeWorkbench *self,
IdeTask *task)
{
g_assert (IDE_IS_WORKBENCH (self));
g_assert (IDE_IS_TASK (task));
g_clear_object (&self->addins);
ide_workbench_foreach_workspace (self, (GtkCallback)gtk_widget_destroy, NULL);
_ide_foundry_unload_async (self->context,
ide_task_get_cancellable (task),
ide_workbench_unload_foundry_cb,
g_object_ref (task));
}
static void
ide_workbench_unload_project_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
IdeWorkbenchAddin *addin = (IdeWorkbenchAddin *)object;
g_autoptr(IdeTask) task = user_data;
g_autoptr(GError) error = NULL;
IdeWorkbench *self;
GPtrArray *addins;
g_assert (IDE_IS_WORKBENCH_ADDIN (addin));
g_assert (G_IS_ASYNC_RESULT (result));
g_assert (IDE_IS_TASK (task));
self = ide_task_get_source_object (task);
addins = ide_task_get_task_data (task);
g_assert (IDE_IS_WORKBENCH (self));
g_assert (addins != NULL);
g_assert (addins->len > 0);
if (!ide_workbench_addin_unload_project_finish (addin, result, &error))
{
if (!ignore_error (error))
g_warning ("%s failed to unload project: %s",
G_OBJECT_TYPE_NAME (addin), error->message);
}
g_ptr_array_remove (addins, addin);
if (addins->len == 0)
ide_workbench_unload_project_completed (self, task);
}
/**
* ide_workbench_unload_async:
* @self: an #IdeWorkbench
* @cancellable: (nullable): a #GCancellable
* @callback: a #GAsyncReadyCallback to execute upon completion
* @user_data: closure data for @callback
*
* Asynchronously unloads the workbench.
*
* All #IdeWorkspace windows will be closed after calling this
* function.
*
* Since: 3.32
*/
void
ide_workbench_unload_async (IdeWorkbench *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(IdeTask) task = NULL;
g_autoptr(GPtrArray) addins = NULL;
GApplication *app;
g_return_if_fail (IDE_IS_WORKBENCH (self));
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
task = ide_task_new (self, cancellable, callback, user_data);
ide_task_set_source_tag (task, ide_workbench_unload_async);
if (self->unloaded)
{
ide_task_return_boolean (task, TRUE);
return;
}
self->unloaded = TRUE;
/* Keep the GApplication alive for the lifetime of the task */
app = g_application_get_default ();
g_signal_connect_object (task,
"notify::completed",
G_CALLBACK (g_application_release),
app,
G_CONNECT_SWAPPED);
g_application_hold (app);
/*
* Remove our workbench from the application, so that no new
* open-file requests can keep us alive while we're shutting
* down.
*/
ide_application_remove_workbench (IDE_APPLICATION (app), self);
/* If we haven't loaded a project, then there is nothing to
* do right now, just let ide_workbench_addin_unload() be called
* when the workbench disposes.
*/
if (self->project_info == NULL)
{
ide_workbench_unload_project_completed (self, g_steal_pointer (&task));
return;
}
addins = ide_workbench_collect_addins (self);
ide_task_set_task_data (task, g_ptr_array_ref (addins), g_ptr_array_unref);
if (addins->len == 0)
{
ide_workbench_unload_project_completed (self, task);
return;
}
for (guint i = 0; i < addins->len; i++)
{
IdeWorkbenchAddin *addin = g_ptr_array_index (addins, i);
ide_workbench_addin_unload_project_async (addin,
self->project_info,
ide_task_get_cancellable (task),
ide_workbench_unload_project_cb,
g_object_ref (task));
}
/* Since the g_steal_pointer() just before doesn't always run, ensure the
* task isn't freed while it hasn't yet finished running asynchronously.
*/
task = NULL;
}
/**
* ide_workbench_unload_finish:
* @self: an #IdeWorkbench
* @result: a #GAsyncResult provided to callback
* @error: a location for a #GError, or %NULL
* Completes a request to unload the workbench.
*
* Returns: %TRUE if the workbench was unloaded successfully,
* otherwise %FALSE and @error is set.
*
* Since: 3.32
*/
gboolean
ide_workbench_unload_finish (IdeWorkbench *self,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (IDE_IS_WORKBENCH (self), FALSE);
g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
return ide_task_propagate_boolean (IDE_TASK (result), error);
}
static void
ide_workbench_open_all_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
IdeWorkbench *self = (IdeWorkbench *)object;
g_autoptr(IdeTask) task = user_data;
g_autoptr(GError) error = NULL;
gint *n_active;
g_assert (IDE_IS_WORKBENCH (self));
g_assert (G_IS_ASYNC_RESULT (result));
g_assert (IDE_IS_TASK (task));
if (!ide_workbench_open_finish (self, result, &error))
g_message ("Failed to open file: %s", error->message);
n_active = ide_task_get_task_data (task);
g_assert (n_active != NULL);
(*n_active)--;
if (*n_active == 0)
ide_task_return_boolean (task, TRUE);
}
/**
* ide_workbench_open_all_async:
* @self: an #IdeWorkbench
* @files: (array length=n_files): an array of #GFile
* @n_files: number of #GFiles contained in @files
* @hint: (nullable): an optional hint about what addin to use
* @cancellable: (nullable): a #GCancellable
* @callback: a #GAsyncReadyCallback to execute upon completion
* @user_data: closure data for @callback
*
* Requests that the workbench open all of the #GFile denoted by @files.
*
* If @hint is provided, that will be used to determine what workbench
* addin to use when opening the file. The @hint name should match the
* module name of the plugin.
*
* Call ide_workbench_open_finish() from @callback to complete this
* operation.
*
* Since: 3.32
*/
void
ide_workbench_open_all_async (IdeWorkbench *self,
GFile **files,
guint n_files,
const gchar *hint,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(IdeTask) task = NULL;
g_autoptr(GPtrArray) ar = NULL;
gint *n_active;
g_return_if_fail (IDE_IS_WORKBENCH (self));
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
task = ide_task_new (self, cancellable, callback, user_data);
ide_task_set_source_tag (task, ide_workbench_open_all_async);
if (n_files == 0)
{
ide_task_return_boolean (task, TRUE);
return;
}
ar = g_ptr_array_new_full (n_files, g_object_unref);
for (guint i = 0; i < n_files; i++)
g_ptr_array_add (ar, g_object_ref (files[i]));
n_active = g_new0 (gint, 1);
*n_active = ar->len;
ide_task_set_task_data (task, n_active, g_free);
for (guint i = 0; i < ar->len; i++)
{
GFile *file = g_ptr_array_index (ar, i);
ide_workbench_open_async (self,
file,
hint,
IDE_BUFFER_OPEN_FLAGS_NONE,
cancellable,
ide_workbench_open_all_cb,
g_object_ref (task));
}
}
/**
* ide_workbench_open_async:
* @self: an #IdeWorkbench
* @file: a #GFile
* @hint: (nullable): an optional hint about what addin to use
* @flags: optional flags when opening the file
* @cancellable: (nullable): a #GCancellable
* @callback: a #GAsyncReadyCallback to execute upon completion
* @user_data: closure data for @callback
*
* Requests that the workbench open @file.
*
* If @hint is provided, that will be used to determine what workbench
* addin to use when opening the file. The @hint name should match the
* module name of the plugin.
*
* @flags may be ignored by some backends.
*
* Since: 3.32
*/
void
ide_workbench_open_async (IdeWorkbench *self,
GFile *file,
const gchar *hint,
IdeBufferOpenFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_return_if_fail (IDE_IS_WORKBENCH (self));
g_return_if_fail (G_IS_FILE (file));
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
ide_workbench_open_at_async (self,
file,
hint,
-1,
-1,
flags,
cancellable,
callback,
user_data);
}
static void
ide_workbench_open_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
IdeWorkbenchAddin *addin = (IdeWorkbenchAddin *)object;
IdeWorkbenchAddin *next;
g_autoptr(IdeTask) task = user_data;
g_autoptr(GError) error = NULL;
GCancellable *cancellable;
Open *o;
g_assert (IDE_IS_WORKBENCH_ADDIN (addin));
g_assert (G_IS_ASYNC_RESULT (result));
g_assert (IDE_IS_TASK (task));
cancellable = ide_task_get_cancellable (task);
o = ide_task_get_task_data (task);
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
g_assert (o != NULL);
g_assert (o->addins != NULL);
g_assert (o->addins->len > 0);
if (ide_workbench_addin_open_finish (addin, result, &error))
{
ide_task_return_boolean (task, TRUE);
return;
}
g_debug ("%s did not open the file, trying next.",
G_OBJECT_TYPE_NAME (addin));
g_ptr_array_remove (o->addins, addin);
/*
* We failed to open the file, try the next addin that is
* left which said it supported the content-type.
*/
if (o->addins->len == 0)
{
ide_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"Failed to locate addin supporting file");
return;
}
next = g_ptr_array_index (o->addins, 0);
ide_workbench_addin_open_at_async (next,
o->file,
o->content_type,
o->at_line,
o->at_line_offset,
o->flags,
cancellable,
ide_workbench_open_cb,
g_steal_pointer (&task));
}
static gint
sort_by_priority (gconstpointer a,
gconstpointer b,
gpointer user_data)
{
IdeWorkbenchAddin *addin_a = *(IdeWorkbenchAddin **)a;
IdeWorkbenchAddin *addin_b = *(IdeWorkbenchAddin **)b;
Open *o = user_data;
gint prio_a = 0;
gint prio_b = 0;
if (!ide_workbench_addin_can_open (addin_a, o->file, o->content_type, &prio_a))
return 1;
if (!ide_workbench_addin_can_open (addin_b, o->file, o->content_type, &prio_b))
return -1;
if (prio_a < prio_b)
return -1;
else if (prio_a > prio_b)
return 1;
else
return 0;
}
static void
ide_workbench_open_query_info_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GFile *file = (GFile *)object;
g_autoptr(IdeTask) task = user_data;
g_autoptr(GFileInfo) info = NULL;
g_autoptr(GError) error = NULL;
IdeWorkbenchAddin *first;
GCancellable *cancellable;
Open *o;
g_assert (G_IS_FILE (file));
g_assert (G_IS_ASYNC_RESULT (result));
g_assert (IDE_IS_TASK (task));
cancellable = ide_task_get_cancellable (task);
o = ide_task_get_task_data (task);
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
g_assert (o != NULL);
g_assert (o->addins != NULL);
g_assert (o->addins->len > 0);
if ((info = g_file_query_info_finish (file, result, &error)))
o->content_type = g_strdup (g_file_info_get_content_type (info));
/* Remove unsupported addins while iterating backwards so that
* we can preserve the ordering of the array as we go.
*/
for (guint i = o->addins->len; i > 0; i--)
{
IdeWorkbenchAddin *addin = g_ptr_array_index (o->addins, i - 1);
gint prio = G_MAXINT;
if (!ide_workbench_addin_can_open (addin, o->file, o->content_type, &prio))
{
g_ptr_array_remove_index_fast (o->addins, i - 1);
if (o->preferred == addin)
g_clear_object (&o->preferred);
}
}
if (o->addins->len == 0)
{
ide_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"No addins can open the file");
return;
}
/*
* Now sort the addins by priority, so that we can attempt to load them
* in the preferred ordering.
*/
g_ptr_array_sort_with_data (o->addins, sort_by_priority, o);
/*
* Ensure that we place the preferred at the head of the array, so
* that it gets preference over default priorities.
*/
if (o->preferred != NULL)
{
g_ptr_array_insert (o->addins, 0, g_object_ref (o->preferred));
for (guint i = 1; i < o->addins->len; i++)
{
if (g_ptr_array_index (o->addins, i) == (gpointer)o->preferred)
{
g_ptr_array_remove_index (o->addins, i);
break;
}
}
}
/* Now start requesting that addins attempt to load the file. */
first = g_ptr_array_index (o->addins, 0);
ide_workbench_addin_open_at_async (first,
o->file,
o->content_type,
o->at_line,
o->at_line_offset,
o->flags,
cancellable,
ide_workbench_open_cb,
g_steal_pointer (&task));
}
/**
* ide_workbench_open_at_async:
* @self: an #IdeWorkbench
* @file: a #GFile
* @hint: (nullable): an optional hint about what addin to use
* @at_line: the line number to open at, or -1 to ignore
* @at_line_offset: the line offset to open at, or -1 to ignore
* @flags: optional #IdeBufferOpenFlags
* @cancellable: (nullable): a #GCancellable
* @callback: a #GAsyncReadyCallback to execute upon completion
* @user_data: closure data for @callback
*
* Like ide_workbench_open_async(), this allows opening a file
* within the workbench. However, it also allows specifying a
* line and column offset within the file to focus. Usually, this
* only makes sense for files that can be opened in an editor.
*
* @at_line and @at_line_offset may be < 0 to ignore the parameters.
*
* @flags may be ignored by some backends
*
* Use ide_workbench_open_finish() to receive teh result of this
* asynchronous operation.
*
* Since: 3.32
*/
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)
{
g_autoptr(IdeTask) task = NULL;
g_autoptr(GPtrArray) addins = NULL;
IdeWorkbench *other;
Open *o;
g_return_if_fail (IDE_IS_WORKBENCH (self));
g_return_if_fail (G_IS_FILE (file));
g_return_if_fail (self->unloaded == FALSE);
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
/* Possibly re-route opening the file to another workbench if we
* discover the file is a better fit over there.
*/
other = ide_application_find_workbench_for_file (IDE_APPLICATION_DEFAULT, file);
if (other != NULL && other != self)
{
ide_workbench_open_at_async (other,
file,
hint,
at_line,
at_line_offset,
flags,
cancellable,
callback,
user_data);
return;
}
/* Canonicalize parameters */
if (at_line < 0)
at_line = -1;
if (at_line_offset < 0)
at_line_offset = -1;
task = ide_task_new (self, cancellable, callback, user_data);
ide_task_set_source_tag (task, ide_workbench_open_at_async);
/*
* Make sure we might have an addin to load after discovering
* the files content-type.
*/
if (!(addins = ide_workbench_collect_addins (self)) || addins->len == 0)
{
ide_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"No addins could open the file");
return;
}
o = g_slice_new0 (Open);
o->addins = g_ptr_array_ref (addins);
if (hint != NULL)
o->preferred = ide_workbench_find_addin (self, hint);
o->file = g_object_ref (file);
o->hint = g_strdup (hint);
o->flags = flags;
o->at_line = at_line;
o->at_line_offset = at_line_offset;
ide_task_set_task_data (task, o, open_free);
g_file_query_info_async (file,
G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
G_FILE_QUERY_INFO_NONE,
G_PRIORITY_DEFAULT,
cancellable,
ide_workbench_open_query_info_cb,
g_steal_pointer (&task));
}
/**
* ide_workbench_open_finish:
* @self: an #IdeWorkbench
* @result: a #GAsyncResult provided to callback
* @error: a location for a #GError, or %NULL
*
* Completes a request to open a file using either
* ide_workbench_open_async() or ide_workbench_open_at_async().
*
* Returns: %TRUE if the file was successfully opened; otherwise
* %FALSE and @error is set.
*
* Since: 3.32
*/
gboolean
ide_workbench_open_finish (IdeWorkbench *self,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (IDE_IS_WORKBENCH (self), FALSE);
g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
return ide_task_propagate_boolean (IDE_TASK (result), error);
}
/**
* ide_workbench_get_current_workspace:
* @self: a #IdeWorkbench
*
* Gets the most recently focused workspace, which may be used to
* deliver events such as opening new pages.
*
* Returns: (transfer none) (nullable): an #IdeWorkspace or %NULL
*
* Since: 3.32
*/
IdeWorkspace *
ide_workbench_get_current_workspace (IdeWorkbench *self)
{
g_return_val_if_fail (IDE_IS_WORKBENCH (self), NULL);
if (self->mru_queue.length > 0)
return IDE_WORKSPACE (self->mru_queue.head->data);
return NULL;
}
/**
* ide_workbench_activate:
* @self: a #IdeWorkbench
*
* This function will attempt to raise the most recently focused workspace.
*
* Since: 3.32
*/
void
ide_workbench_activate (IdeWorkbench *self)
{
IdeWorkspace *workspace;
g_return_if_fail (IDE_IS_WORKBENCH (self));
if ((workspace = ide_workbench_get_current_workspace (self)))
ide_workbench_focus_workspace (self, workspace);
}
static void
ide_workbench_propagate_vcs_cb (PeasExtensionSet *set,
PeasPluginInfo *plugin_info,
PeasExtension *exten,
gpointer user_data)
{
IdeWorkbenchAddin *addin = (IdeWorkbenchAddin *)exten;
IdeVcs *vcs = user_data;
g_assert (PEAS_IS_EXTENSION_SET (set));
g_assert (plugin_info != NULL);
g_assert (IDE_IS_WORKBENCH_ADDIN (addin));
g_assert (!vcs || IDE_IS_VCS (vcs));
ide_workbench_addin_vcs_changed (addin, vcs);
}
/**
* ide_workbench_get_vcs:
* @self: a #IdeWorkbench
*
* Gets the #IdeVcs that has been loaded for the workbench, if any.
*
* Returns: (transfer none) (nullable): an #IdeVcs or %NULL
*
* Since: 3.32
*/
IdeVcs *
ide_workbench_get_vcs (IdeWorkbench *self)
{
g_return_val_if_fail (IDE_IS_WORKBENCH (self), NULL);
return self->vcs;
}
/**
* ide_workbench_get_vcs_monitor:
* @self: a #IdeWorkbench
*
* Gets the #IdeVcsMonitor for the workbench, if any.
*
* Returns: (transfer none) (nullable): an #IdeVcsMonitor or %NULL
*
* Since: 3.32
*/
IdeVcsMonitor *
ide_workbench_get_vcs_monitor (IdeWorkbench *self)
{
g_return_val_if_fail (IDE_IS_WORKBENCH (self), NULL);
return self->vcs_monitor;
}
static void
remove_non_matching_vcs_cb (IdeObject *child,
IdeVcs *vcs)
{
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_OBJECT (child));
g_assert (IDE_IS_VCS (vcs));
if (IDE_IS_VCS (child) && IDE_VCS (child) != vcs)
ide_object_destroy (child);
}
static void
ide_workbench_vcs_notify_branch_name_cb (IdeWorkbench *self,
GParamSpec *pspec,
IdeVcs *vcs)
{
IdeBuildManager *build_manager;
IDE_ENTRY;
g_assert (IDE_IS_WORKBENCH (self));
g_assert (IDE_IS_VCS (vcs));
if (!ide_workbench_has_project (self))
IDE_EXIT;
build_manager = ide_build_manager_from_context (self->context);
ide_build_manager_invalidate (build_manager);
IDE_EXIT;
}
/**
* ide_workbench_set_vcs:
* @self: a #IdeWorkbench
* @vcs: (nullable): an #IdeVcs
*
* Sets the #IdeVcs for the workbench.
*
* Since: 3.32
*/
void
ide_workbench_set_vcs (IdeWorkbench *self,
IdeVcs *vcs)
{
g_autoptr(IdeVcs) local_vcs = NULL;
g_autoptr(GFile) local_workdir = NULL;
g_autoptr(GFile) workdir = NULL;
g_return_if_fail (IDE_IS_MAIN_THREAD ());
g_return_if_fail (IDE_IS_WORKBENCH (self));
g_return_if_fail (!vcs || IDE_IS_VCS (vcs));
if (self->vcs != NULL && vcs == self->vcs)
return;
if (vcs == NULL)
{
local_workdir = ide_context_ref_workdir (self->context);
vcs = local_vcs = IDE_VCS (ide_directory_vcs_new (local_workdir));
}
g_set_object (&self->vcs, vcs);
ide_object_append (IDE_OBJECT (self->context), IDE_OBJECT (vcs));
ide_object_foreach (IDE_OBJECT (self->context),
(GFunc)remove_non_matching_vcs_cb,
vcs);
if ((workdir = ide_vcs_get_workdir (vcs)))
ide_context_set_workdir (self->context, workdir);
ide_vcs_monitor_set_vcs (self->vcs_monitor, self->vcs);
peas_extension_set_foreach (self->addins,
ide_workbench_propagate_vcs_cb,
self->vcs);
g_signal_connect_object (vcs,
"notify::branch-name",
G_CALLBACK (ide_workbench_vcs_notify_branch_name_cb),
self,
G_CONNECT_SWAPPED);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_VCS]);
}
/**
* ide_workbench_get_build_system:
* @self: a #IdeWorkbench
*
* Gets the #IdeBuildSystem for the workbench, if any.
*
* Returns: (transfer none) (nullable): an #IdeBuildSystem or %NULL
*
* Since: 3.32
*/
IdeBuildSystem *
ide_workbench_get_build_system (IdeWorkbench *self)
{
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
g_return_val_if_fail (IDE_IS_WORKBENCH (self), NULL);
return self->build_system;
}
static void
remove_non_matching_build_systems_cb (IdeObject *child,
IdeBuildSystem *build_system)
{
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_OBJECT (child));
g_assert (IDE_IS_BUILD_SYSTEM (build_system));
if (IDE_IS_BUILD_SYSTEM (child) && IDE_BUILD_SYSTEM (child) != build_system)
ide_object_destroy (child);
}
/**
* ide_workbench_set_build_system:
* @self: a #IdeWorkbench
* @build_system: (nullable): an #IdeBuildSystem or %NULL
*
* Sets the #IdeBuildSystem for the workbench.
*
* If @build_system is %NULL, then a fallback build system will be used
* instead. It does not provide building capabilities, but allows for some
* components that require a build system to continue functioning.
*
* Since: 3.32
*/
void
ide_workbench_set_build_system (IdeWorkbench *self,
IdeBuildSystem *build_system)
{
g_autoptr(IdeBuildSystem) local_build_system = NULL;
IdeBuildManager *build_manager;
g_return_if_fail (IDE_IS_WORKBENCH (self));
g_return_if_fail (!build_system || IDE_IS_BUILD_SYSTEM (build_system));
if (build_system == self->build_system)
return;
/* We want there to always be a build system available so that various
* plugins don't need lots of extra code to handle the %NULL case. So
* if @build_system is %NULL, then we'll create a fallback build system
* and assign that instead.
*/
if (build_system == NULL)
build_system = local_build_system = ide_fallback_build_system_new ();
/* We want to add our new build system before removing the old build
* system to ensure there is always an #IdeBuildSystem child of the
* IdeContext.
*/
g_set_object (&self->build_system, build_system);
ide_object_append (IDE_OBJECT (self->context), IDE_OBJECT (build_system));
/* Now remove any previous build-system from the context */
ide_object_foreach (IDE_OBJECT (self->context),
(GFunc)remove_non_matching_build_systems_cb,
build_system);
/* Ask the build-manager to setup a new pipeline */
if ((build_manager = ide_context_peek_child_typed (self->context, IDE_TYPE_BUILD_MANAGER)))
ide_build_manager_invalidate (build_manager);
}
/**
* ide_workbench_get_workspace_by_type:
* @self: a #IdeWorkbench
* @type: a #GType of a subclass of #IdeWorkspace
*
* Gets the most-recently-used workspace that matches @type.
*
* Returns: (transfer none) (nullable): an #IdeWorkspace or %NULL
*
* Since: 3.32
*/
IdeWorkspace *
ide_workbench_get_workspace_by_type (IdeWorkbench *self,
GType type)
{
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
g_return_val_if_fail (IDE_IS_WORKBENCH (self), NULL);
g_return_val_if_fail (g_type_is_a (type, IDE_TYPE_WORKSPACE), NULL);
for (const GList *iter = self->mru_queue.head; iter; iter = iter->next)
{
if (G_TYPE_CHECK_INSTANCE_TYPE (iter->data, type))
return IDE_WORKSPACE (iter->data);
}
return NULL;
}
gboolean
_ide_workbench_is_last_workspace (IdeWorkbench *self,
IdeWorkspace *workspace)
{
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
g_return_val_if_fail (IDE_IS_WORKBENCH (self), FALSE);
return self->mru_queue.length == 1 &&
g_queue_peek_head (&self->mru_queue) == (gpointer)workspace;
}
/**
* ide_workbench_has_project:
* @self: a #IdeWorkbench
*
* Returns %TRUE if a project is loaded (or currently loading) in the
* workbench.
*
* Returns: %TRUE if the workbench has a project
*
* Since: 3.32
*/
gboolean
ide_workbench_has_project (IdeWorkbench *self)
{
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
g_return_val_if_fail (IDE_IS_WORKBENCH (self), FALSE);
return self->project_info != NULL;
}
/**
* ide_workbench_addin_find_by_module_name:
* @workbench: an #IdeWorkbench
* @module_name: the name of the addin module
*
* Finds the addin (if any) matching the plugin's @module_name.
*
* Returns: (transfer none) (nullable): an #IdeWorkbenchAddin or %NULL
*
* Since: 3.32
*/
IdeWorkbenchAddin *
ide_workbench_addin_find_by_module_name (IdeWorkbench *workbench,
const gchar *module_name)
{
PeasPluginInfo *plugin_info;
PeasExtension *ret = NULL;
PeasEngine *engine;
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
g_return_val_if_fail (IDE_IS_WORKBENCH (workbench), NULL);
g_return_val_if_fail (module_name != NULL, NULL);
if (workbench->addins == NULL)
return NULL;
engine = peas_engine_get_default ();
if ((plugin_info = peas_engine_get_plugin_info (engine, module_name)))
ret = peas_extension_set_get_extension (workbench->addins, plugin_info);
return IDE_WORKBENCH_ADDIN (ret);
}
static void
ide_workbench_resolve_file_worker (IdeTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
ResolveFile *rf = task_data;
g_autofree gchar *basename = NULL;
g_assert (IDE_IS_TASK (task));
g_assert (IDE_IS_WORKBENCH (source_object));
g_assert (rf != NULL);
g_assert (rf->roots != NULL);
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
basename = g_path_get_basename (rf->path);
for (guint i = 0; i < rf->roots->len; i++)
{
GFile *root = g_ptr_array_index (rf->roots, i);
g_autoptr(GFile) child = g_file_get_child (root, rf->path);
g_autoptr(GPtrArray) found = NULL;
if (g_file_query_exists (child, cancellable))
{
ide_task_return_pointer (task, g_steal_pointer (&child), g_object_unref);
return;
}
found = ide_g_file_find_with_depth (root, basename, 0, cancellable);
IDE_PTR_ARRAY_SET_FREE_FUNC (found, g_object_unref);
if (found != NULL && found->len > 0)
{
GFile *match = g_ptr_array_index (found, 0);
ide_task_return_pointer (task, g_object_ref (match), g_object_unref);
return;
}
}
ide_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_NOT_FOUND,
"Failed to locate file %s",
basename);
}
/**
* ide_workbench_resolve_file_async:
* @self: a #IdeWorkbench
* @filename: the filename to discover
*
* This function will try to locate a given file based on the filename,
* possibly resolving it from a build directory, or source directory.
*
* If no file was discovered, some attempt will be made to locate a file
* that matches appropriately.
*
* Since: 3.32
*/
void
ide_workbench_resolve_file_async (IdeWorkbench *self,
const gchar *filename,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(IdeTask) task = NULL;
ResolveFile *rf;
IDE_ENTRY;
g_return_if_fail (IDE_IS_MAIN_THREAD ());
g_return_if_fail (IDE_IS_WORKBENCH (self));
g_return_if_fail (filename != NULL);
task = ide_task_new (self, cancellable, callback, user_data);
ide_task_set_source_tag (task, ide_workbench_resolve_file_async);
rf = g_slice_new0 (ResolveFile);
rf->roots = g_ptr_array_new_with_free_func (g_object_unref);
rf->path = g_strdup (filename);
g_ptr_array_add (rf->roots, ide_context_ref_workdir (self->context));
if (ide_workbench_has_project (self))
{
IdeBuildManager *build_manager = ide_build_manager_from_context (self->context);
IdePipeline *pipeline = ide_build_manager_get_pipeline (build_manager);
if (pipeline != NULL)
{
const gchar *builddir = ide_pipeline_get_builddir (pipeline);
g_ptr_array_add (rf->roots, g_file_new_for_path (builddir));
}
}
ide_task_set_task_data (task, rf, resolve_file_free);
ide_task_run_in_thread (task, ide_workbench_resolve_file_worker);
IDE_EXIT;
}
/**
* ide_workbench_resolve_file_finish:
* @self: a #IdeWorkbench
* @result: a #GAsyncResult
* @error: a location for a #GError
*
* Completes an asynchronous request to ide_workbench_resolve_file_async().
*
* Returns: (transfer full): a #GFile, or %NULL and @error is set
*
* Since: 3.32
*/
GFile *
ide_workbench_resolve_file_finish (IdeWorkbench *self,
GAsyncResult *result,
GError **error)
{
GFile *ret;
IDE_ENTRY;
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
g_return_val_if_fail (IDE_IS_WORKBENCH (self), NULL);
g_return_val_if_fail (IDE_IS_TASK (result), NULL);
ret = ide_task_propagate_pointer (IDE_TASK (result), error);
IDE_RETURN (g_steal_pointer (&ret));
}