static void
gb_project_tree_actions_open_containing_folder (GSimpleAction *action,
                                                GVariant      *variant,
                                                gpointer       user_data)
{
  GbEditorWorkspace *self = user_data;
  GbTreeNode *selected;
  GObject *item;
  GFile *file;

  g_assert (GB_IS_PROJECT_TREE (self));

  if (!(selected = gb_tree_get_selected (GB_TREE (self))) ||
      !(item = gb_tree_node_get_item (selected)) ||
      !(IDE_IS_PROJECT_FILE (item) || IDE_IS_PROJECT_FILES (item)))
    return;

  if (IDE_IS_PROJECT_FILES (item))
    {
      IdeContext *context;
      IdeVcs *vcs;

      context = ide_object_get_context (IDE_OBJECT (item));
      vcs = ide_context_get_vcs (context);
      file = ide_vcs_get_working_directory (vcs);
    }
  else if (!(file = ide_project_file_get_file (IDE_PROJECT_FILE (item))))
    {
      return;
    }

  gb_file_manager_show (file, NULL);
}
static void
gb_project_tree_actions_move_to_trash (GSimpleAction *action,
                                       GVariant      *param,
                                       gpointer       user_data)
{
  GbProjectTree *self = user_data;
  GbWorkbench *workbench;
  IdeContext *context;
  IdeProject *project;
  GbTreeNode *node;
  GFile *file;
  GObject *item;

  g_assert (G_IS_SIMPLE_ACTION (action));
  g_assert (GB_IS_PROJECT_TREE (self));

  workbench = gb_widget_get_workbench (GTK_WIDGET (self));
  context = gb_workbench_get_context (workbench);
  project = ide_context_get_project (context);

  if (!(node = gb_tree_get_selected (GB_TREE (self))) ||
      !(item = gb_tree_node_get_item (node)) ||
      !IDE_IS_PROJECT_FILE (item) ||
      !(file = ide_project_file_get_file (IDE_PROJECT_FILE (item))))
    return;

  ide_project_trash_file_async (project,
                                file,
                                NULL,
                                gb_project_tree_actions__trash_file_cb,
                                g_object_ref (self));
}
static void
gb_project_tree_actions_open_with_editor (GSimpleAction *action,
                                          GVariant      *variant,
                                          gpointer       user_data)
{
  GbWorkbench *workbench;
  GbProjectTree *self = user_data;
  GFileInfo *file_info;
  GFile *file;
  GbTreeNode *selected;
  GObject *item;

  g_assert (GB_IS_PROJECT_TREE (self));

  if (!(selected = gb_tree_get_selected (GB_TREE (self))) ||
      !(item = gb_tree_node_get_item (selected)) ||
      !IDE_IS_PROJECT_FILE (item) ||
      !(file_info = ide_project_file_get_file_info (IDE_PROJECT_FILE (item))) ||
      (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY) ||
      !(file = ide_project_file_get_file (IDE_PROJECT_FILE (item))) ||
      !(workbench = gb_widget_get_workbench (GTK_WIDGET (self))))
    return;

  gb_workbench_open_with_editor (workbench, file);
}
static IdeProjectItem *
ide_project_files_find_child (IdeProjectItem *item,
                              const gchar    *child)
{
  GSequence *children;
  GSequenceIter *iter;

  g_assert (IDE_IS_PROJECT_ITEM (item));
  g_assert (child);

  children = ide_project_item_get_children (item);
  if (!children)
    return NULL;

  for (iter = g_sequence_get_begin_iter (children);
       !g_sequence_iter_is_end (iter);
       iter = g_sequence_iter_next (iter))
    {
      IdeProjectItem *item = g_sequence_get (iter);

      if (IDE_IS_PROJECT_FILE (item))
        {
          IdeProjectFile *file = IDE_PROJECT_FILE (item);
          const gchar *name = ide_project_file_get_name (file);

          if (g_strcmp0 (name, child) == 0)
            return item;
        }
    }

  return NULL;
}
static gboolean
project_file_is_directory (GObject *object)
{
  g_assert (!object || G_IS_OBJECT (object));

  return (IDE_IS_PROJECT_FILE (object) &&
          ide_project_file_get_is_directory (IDE_PROJECT_FILE (object)));
}
/**
 * ide_project_file_get_file_info:
 *
 * Retrieves a #GFileInfo containing the basic information about @file. This
 * includes the name, display name, and file type.
 *
 * Returns: (transfer none): A #GFileInfo
 */
GFileInfo *
ide_project_file_get_file_info (IdeProjectFile *file)
{
  IdeProjectFilePrivate *priv = ide_project_file_get_instance_private (file);

  g_return_val_if_fail (IDE_IS_PROJECT_FILE (file), NULL);

  return priv->file_info;
}
const gchar *
ide_project_file_get_path (IdeProjectFile *self)
{
  IdeProjectFilePrivate *priv = ide_project_file_get_instance_private (self);

  g_return_val_if_fail (IDE_IS_PROJECT_FILE (self), NULL);

  return priv->path;
}
gboolean
ide_project_file_get_is_directory (IdeProjectFile *self)
{
  IdeProjectFilePrivate *priv = ide_project_file_get_instance_private (self);

  g_return_val_if_fail (IDE_IS_PROJECT_FILE (self), FALSE);

  if (priv->file_info)
    return (g_file_info_get_file_type (priv->file_info) == G_FILE_TYPE_DIRECTORY);

  return FALSE;
}
void
ide_project_file_set_file (IdeProjectFile *self,
                           GFile          *file)
{
  IdeProjectFilePrivate *priv = ide_project_file_get_instance_private (self);

  g_return_if_fail (IDE_IS_PROJECT_FILE (self));
  g_return_if_fail (!file || G_IS_FILE (file));

  if (g_set_object (&priv->file, file))
    g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FILE]);
}
const gchar *
ide_project_file_get_name (IdeProjectFile *self)
{
  IdeProjectFilePrivate *priv = ide_project_file_get_instance_private (self);
  const gchar *name;

  g_return_val_if_fail (IDE_IS_PROJECT_FILE (self), NULL);

  name = g_file_info_get_display_name (priv->file_info);
  if (name == NULL)
    name = g_file_info_get_name (priv->file_info);

  return name;
}
static void
ide_project_file_set_path (IdeProjectFile *self,
                           const gchar    *path)
{
  IdeProjectFilePrivate *priv = ide_project_file_get_instance_private (self);

  g_return_if_fail (IDE_IS_PROJECT_FILE (self));

  if (priv->path != path)
    {
      g_free (priv->path);
      priv->path = g_strdup (path);
      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PATH]);
    }
}
void
ide_project_file_set_file_info (IdeProjectFile *file,
                                GFileInfo      *file_info)
{
  IdeProjectFilePrivate *priv = ide_project_file_get_instance_private (file);

  g_return_if_fail (IDE_IS_PROJECT_FILE (file));
  g_return_if_fail (!file_info || G_IS_FILE_INFO (file_info));

  if (g_set_object (&priv->file_info, file_info))
    {
      g_object_notify_by_pspec (G_OBJECT (file), properties [PROP_FILE_INFO]);
      g_object_notify_by_pspec (G_OBJECT (file), properties [PROP_NAME]);
    }
}
static gint
project_item_equal_func (GFile   *key,
                         GObject *item)
{
  g_assert (G_IS_FILE (key));
  g_assert (IDE_IS_PROJECT_ITEM (item));

  if (IDE_IS_PROJECT_FILE (item))
    {
      GFile *file;

      file = ide_project_file_get_file (IDE_PROJECT_FILE (item));
      g_assert (G_IS_FILE (file));

      return g_file_equal (key, file);
    }

  return FALSE;
}
void
gb_project_tree_actions_update (GbProjectTree *self)
{
  GActionGroup *group;
  GbTreeNode *selection;
  GObject *item = NULL;

  IDE_ENTRY;

  g_assert (GB_IS_PROJECT_TREE (self));

  group = gtk_widget_get_action_group (GTK_WIDGET (self), "project-tree");
  g_assert (G_IS_SIMPLE_ACTION_GROUP (group));

  selection = gb_tree_get_selected (GB_TREE (self));
  if (selection != NULL)
    item = gb_tree_node_get_item (selection);

  action_set (group, "new-file",
              "enabled", (IDE_IS_PROJECT_FILE (item) || IDE_IS_PROJECT_FILES (item)),
              NULL);
  action_set (group, "new-directory",
              "enabled", (IDE_IS_PROJECT_FILE (item) || IDE_IS_PROJECT_FILES (item)),
              NULL);
  action_set (group, "open",
              "enabled", (IDE_IS_PROJECT_FILE (item) && !project_file_is_directory (item)),
              NULL);
  action_set (group, "open-with-editor",
              "enabled", (IDE_IS_PROJECT_FILE (item) && !project_file_is_directory (item)),
              NULL);
  action_set (group, "open-containing-folder",
              "enabled", (IDE_IS_PROJECT_FILE (item) || IDE_IS_PROJECT_FILES (item)),
              NULL);
  action_set (group, "rename-file",
              "enabled", IDE_IS_PROJECT_FILE (item),
              NULL);
  action_set (group, "move-to-trash",
              "enabled", (IDE_IS_PROJECT_FILE (item) && !project_file_is_directory (item)),
              NULL);

  IDE_EXIT;
}
static void
gb_project_tree_actions_open_with (GSimpleAction *action,
                                   GVariant      *variant,
                                   gpointer       user_data)
{
  g_autoptr(GDesktopAppInfo) app_info = NULL;
  g_autoptr(GdkAppLaunchContext) launch_context = NULL;
  GbProjectTree *self = user_data;
  GbTreeNode *selected;
  GbWorkbench *workbench;
  GdkDisplay *display;
  GFileInfo *file_info;
  GFile *file;
  const gchar *app_id;
  GObject *item;
  GList *files;

  g_assert (GB_IS_PROJECT_TREE (self));
  g_assert (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING));

  if (!(workbench = gb_widget_get_workbench (GTK_WIDGET (self))) ||
      !(selected = gb_tree_get_selected (GB_TREE (self))) ||
      !(item = gb_tree_node_get_item (selected)) ||
      !IDE_IS_PROJECT_FILE (item) ||
      !(app_id = g_variant_get_string (variant, NULL)) ||
      !(file_info = ide_project_file_get_file_info (IDE_PROJECT_FILE (item))) ||
      !(file = ide_project_file_get_file (IDE_PROJECT_FILE (item))) ||
      !(app_info = g_desktop_app_info_new (app_id)))
    return;

  display = gtk_widget_get_display (GTK_WIDGET (self));
  launch_context = gdk_display_get_app_launch_context (display);

  files = g_list_append (NULL, file);
  g_app_info_launch (G_APP_INFO (app_info), files, G_APP_LAUNCH_CONTEXT (launch_context), NULL);
  g_list_free (files);
}
static void
gb_project_tree_actions_rename_file (GSimpleAction *action,
                                     GVariant      *variant,
                                     gpointer       user_data)
{
  GbProjectTree *self = user_data;
  GbTreeNode *selected;
  GtkPopover *popover;
  GObject *item;
  GFile *file;
  GFileInfo *file_info;
  gboolean is_dir;

  g_assert (GB_IS_PROJECT_TREE (self));

  if (!(selected = gb_tree_get_selected (GB_TREE (self))) ||
      !(item = gb_tree_node_get_item (selected)) ||
      !IDE_IS_PROJECT_FILE (item) ||
      !(file = ide_project_file_get_file (IDE_PROJECT_FILE (item))) ||
      !(file_info = ide_project_file_get_file_info (IDE_PROJECT_FILE (item))))
    return;

  is_dir = (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY);

  popover = g_object_new (GB_TYPE_RENAME_FILE_POPOVER,
                          "file", file,
                          "is-directory", is_dir,
                          "position", GTK_POS_RIGHT,
                          NULL);
  g_signal_connect_object (popover,
                           "rename-file",
                           G_CALLBACK (gb_project_tree_actions__rename_file_cb),
                           self,
                           G_CONNECT_SWAPPED);
  gb_tree_node_show_popover (selected, popover);
}
void
ide_project_files_add_file (IdeProjectFiles *self,
                            IdeProjectFile  *file)
{
  IdeProjectItem *item = (IdeProjectItem *)self;
  g_autoptr(GFile) parent = NULL;
  g_autofree gchar *path = NULL;
  IdeContext *context;
  IdeVcs *vcs;
  GFile *workdir;
  GFile *gfile;
  gchar **parts;
  gsize i;

  g_return_if_fail (IDE_IS_PROJECT_FILES (self));
  g_return_if_fail (IDE_IS_PROJECT_FILE (file));

  context = ide_object_get_context (IDE_OBJECT (self));
  vcs = ide_context_get_vcs (context);
  workdir = ide_vcs_get_working_directory (vcs);

  gfile = ide_project_file_get_file (file);
  parent = g_file_get_parent (gfile);
  path = g_file_get_relative_path (workdir, parent);

  if (path == NULL)
  {
    ide_project_item_append (IDE_PROJECT_ITEM (self), IDE_PROJECT_ITEM (file));
    return;
  }

  parts = g_strsplit (path, G_DIR_SEPARATOR_S, 0);

  for (i = 0; parts [i]; i++)
    {
      IdeProjectItem *found;

      found = ide_project_files_find_child (item, parts [i]);

      if (found == NULL)
        {
          g_autoptr(GFileInfo) file_info = NULL;
          g_autofree gchar *child_path = NULL;
          IdeProjectItem *child;
          const gchar *item_path;
          g_autoptr(GFile) item_file = NULL;

          file_info = g_file_info_new ();
          g_file_info_set_file_type (file_info, G_FILE_TYPE_DIRECTORY);
          g_file_info_set_display_name (file_info, parts [i]);
          g_file_info_set_name (file_info, parts [i]);

          item_path = ide_project_file_get_path (IDE_PROJECT_FILE (item));
          child_path = g_strjoin (G_DIR_SEPARATOR_S, item_path, parts [i], NULL);
          item_file = g_file_get_child (workdir, child_path);

          child = g_object_new (IDE_TYPE_PROJECT_FILE,
                                "context", context,
                                "parent", item,
                                "path", path,
                                "file", item_file,
                                "file-info", file_info,
                                NULL);
          ide_project_item_append (item, child);

          item = child;
        }
      else
        {
          item = found;
        }
    }

  ide_project_item_append (item, IDE_PROJECT_ITEM (file));

  g_strfreev (parts);

}
static void
gb_project_tree_actions_new (GbProjectTree *self,
                             GFileType      file_type)
{
  GbTreeNode *selected;
  GObject *item;
  GtkPopover *popover;
  IdeProjectFile *project_file;
  GFile *file = NULL;
  gboolean is_dir;

  g_assert (GB_IS_PROJECT_TREE (self));
  g_assert ((file_type == G_FILE_TYPE_DIRECTORY) ||
            (file_type == G_FILE_TYPE_REGULAR));

again:
  if (!(selected = gb_tree_get_selected (GB_TREE (self))) ||
      !(item = gb_tree_node_get_item (selected)) ||
      !(IDE_IS_PROJECT_FILES (item) || IDE_IS_PROJECT_FILE (item)))
    return;

  if (IDE_IS_PROJECT_FILE (item))
    {
      if (!(project_file = IDE_PROJECT_FILE (item)) ||
          !(file = ide_project_file_get_file (project_file)))
        return;
      is_dir = project_file_is_directory (item);
    }
  else if (IDE_IS_PROJECT_FILES (item))
    {
      IdeContext *context;
      IdeVcs *vcs;

      context = ide_object_get_context (IDE_OBJECT (item));
      vcs = ide_context_get_vcs (context);
      file = ide_vcs_get_working_directory (vcs);
      is_dir = TRUE;
    }

  g_assert (G_IS_FILE (file));

  /*
   * If this item is an IdeProjectFile and not a directory, then we really
   * want to create a sibling.
   */
  if (!is_dir)
    {
      GtkTreePath *path;

      selected = gb_tree_node_get_parent (selected);
      gb_tree_node_select (selected);
      path = gb_tree_node_get_path (selected);
      gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self), path, NULL, FALSE, 0, 0);
      gtk_tree_path_free (path);

      goto again;
    }

  if ((self->expanded_in_new = !gb_tree_node_get_expanded (selected)))
    gb_tree_node_expand (selected, FALSE);

  popover = g_object_new (GB_TYPE_NEW_FILE_POPOVER,
                          "directory", file,
                          "file-type", file_type,
                          "position", GTK_POS_RIGHT,
                          NULL);
  g_signal_connect_object (popover,
                           "create-file",
                           G_CALLBACK (gb_project_tree_actions__popover_create_file_cb),
                           self,
                           G_CONNECT_SWAPPED);
  g_signal_connect_object (popover,
                           "closed",
                           G_CALLBACK (gb_project_tree_actions__popover_closed_cb),
                           self,
                           G_CONNECT_SWAPPED);

  gb_tree_node_show_popover (selected, popover);
}