[gnome-builder: 111/139] project-tree: rewrite project tree using libide-tree



commit 91845ac3f45d2ab498d48148e052dbfe2a476a33
Author: Christian Hergert <chergert redhat com>
Date:   Wed Jan 9 17:34:02 2019 -0800

    project-tree: rewrite project tree using libide-tree
    
    This provides a new implementation of the project-tree using updated
    designs from libide including libide-tree. Much of the code involving
    popovers has been simplified in terms of async tracking. Some features
    have been broken out into other plugins.

 src/plugins/project-tree/gb-new-file-popover.h     |   38 -
 src/plugins/project-tree/gb-project-file.c         |  302 ------
 src/plugins/project-tree/gb-project-file.h         |   48 -
 src/plugins/project-tree/gb-project-tree-actions.c | 1004 --------------------
 src/plugins/project-tree/gb-project-tree-addin.c   |  131 ---
 src/plugins/project-tree/gb-project-tree-builder.c |  950 ------------------
 src/plugins/project-tree/gb-project-tree-builder.h |   33 -
 .../project-tree/gb-project-tree-editor-addin.c    |  121 ---
 .../project-tree/gb-project-tree-shortcuts.c       |   74 --
 src/plugins/project-tree/gb-project-tree.c         |  631 ------------
 src/plugins/project-tree/gb-project-tree.h         |   46 -
 src/plugins/project-tree/gb-rename-file-popover.h  |   34 -
 src/plugins/project-tree/gb-vcs-tree-builder.c     |  177 ----
 ...b-new-file-popover.c => gbp-new-file-popover.c} |  227 +++--
 src/plugins/project-tree/gbp-new-file-popover.h    |   48 +
 ...new-file-popover.ui => gbp-new-file-popover.ui} |    2 +-
 src/plugins/project-tree/gbp-project-tree-addin.c  |  896 +++++++++++++++++
 ...oject-tree-addin.h => gbp-project-tree-addin.h} |    8 +-
 .../project-tree/gbp-project-tree-pane-actions.c   |  634 ++++++++++++
 src/plugins/project-tree/gbp-project-tree-pane.c   |   62 ++
 ...tree-editor-addin.h => gbp-project-tree-pane.h} |   10 +-
 src/plugins/project-tree/gbp-project-tree-pane.ui  |   20 +
 ...t-tree-private.h => gbp-project-tree-private.h} |   24 +-
 .../gbp-project-tree-workspace-addin.c             |  102 ++
 ...uilder.h => gbp-project-tree-workspace-addin.h} |   12 +-
 src/plugins/project-tree/gbp-project-tree.c        |  178 ++++
 ...b-project-tree-actions.h => gbp-project-tree.h} |   11 +-
 ...me-file-popover.c => gbp-rename-file-popover.c} |  178 +++-
 src/plugins/project-tree/gbp-rename-file-popover.h |   42 +
 ...-file-popover.ui => gbp-rename-file-popover.ui} |    2 +-
 src/plugins/project-tree/gtk/menus.ui              |  108 +--
 src/plugins/project-tree/meson.build               |   49 +-
 src/plugins/project-tree/project-tree-plugin.c     |   21 +-
 .../project-tree/project-tree.gresource.xml        |   11 +-
 src/plugins/project-tree/project-tree.plugin       |   14 +-
 src/plugins/project-tree/themes/shared.css         |    8 +
 36 files changed, 2362 insertions(+), 3894 deletions(-)
---
diff --git a/src/plugins/project-tree/gb-new-file-popover.c b/src/plugins/project-tree/gbp-new-file-popover.c
similarity index 51%
rename from src/plugins/project-tree/gb-new-file-popover.c
rename to src/plugins/project-tree/gbp-new-file-popover.c
index f71220977..fa24efc17 100644
--- a/src/plugins/project-tree/gb-new-file-popover.c
+++ b/src/plugins/project-tree/gbp-new-file-popover.c
@@ -1,4 +1,4 @@
-/* gb-new-file-popover.c
+/* gbp-new-file-popover.c
  *
  * Copyright 2015-2019 Christian Hergert <christian hergert me>
  *
@@ -18,18 +18,21 @@
  * SPDX-License-Identifier: GPL-3.0-or-later
  */
 
+#define G_LOG_DOMAIN "gbp-new-file-popover"
+
 #include <glib/gi18n.h>
-#include <ide.h>
+#include <libide-gui.h>
+#include <libide-threading.h>
 
-#include "gb-new-file-popover.h"
+#include "gbp-new-file-popover.h"
 
-struct _GbNewFilePopover
+struct _GbpNewFilePopover
 {
   GtkPopover    parent_instance;
 
   GFileType     file_type;
   GFile        *directory;
-  GCancellable *cancellable;
+  IdeTask      *task;
 
   GtkButton    *button;
   GtkEntry     *entry;
@@ -37,31 +40,26 @@ struct _GbNewFilePopover
   GtkLabel     *title;
 };
 
-G_DEFINE_TYPE (GbNewFilePopover, gb_new_file_popover, GTK_TYPE_POPOVER)
+G_DEFINE_TYPE (GbpNewFilePopover, gbp_new_file_popover, GTK_TYPE_POPOVER)
 
 enum {
   PROP_0,
   PROP_DIRECTORY,
   PROP_FILE_TYPE,
-  LAST_PROP
-};
-
-enum {
-  CREATE_FILE,
-  LAST_SIGNAL
+  N_PROPS
 };
 
-static GParamSpec *properties [LAST_PROP];
-static guint       signals [LAST_SIGNAL];
+static GParamSpec *properties [N_PROPS];
 
 static void
-gb_new_file_popover__button_clicked (GbNewFilePopover *self,
+gbp_new_file_popover_button_clicked (GbpNewFilePopover *self,
                                      GtkButton        *button)
 {
   g_autoptr(GFile) file = NULL;
+  g_autoptr(IdeTask) task = NULL;
   const gchar *path;
 
-  g_assert (GB_IS_NEW_FILE_POPOVER (self));
+  g_assert (GBP_IS_NEW_FILE_POPOVER (self));
   g_assert (GTK_IS_BUTTON (button));
 
   if (self->directory == NULL)
@@ -73,14 +71,15 @@ gb_new_file_popover__button_clicked (GbNewFilePopover *self,
 
   file = g_file_get_child (self->directory, path);
 
-  g_signal_emit (self, signals [CREATE_FILE], 0, file, self->file_type);
+  if ((task = g_steal_pointer (&self->task)))
+    ide_task_return_pointer (task, g_steal_pointer (&file), g_object_unref);
 }
 
 static void
-gb_new_file_popover__entry_activate (GbNewFilePopover *self,
+gbp_new_file_popover_entry_activate (GbpNewFilePopover *self,
                                      GtkEntry         *entry)
 {
-  g_assert (GB_IS_NEW_FILE_POPOVER (self));
+  g_assert (GBP_IS_NEW_FILE_POPOVER (self));
   g_assert (GTK_IS_ENTRY (entry));
 
   if (gtk_widget_get_sensitive (GTK_WIDGET (self->button)))
@@ -88,13 +87,13 @@ gb_new_file_popover__entry_activate (GbNewFilePopover *self,
 }
 
 static void
-gb_new_file_popover__query_info_cb (GObject      *object,
+gbp_new_file_popover_query_info_cb (GObject      *object,
                                     GAsyncResult *result,
                                     gpointer      user_data)
 {
   GFile *file = (GFile *)object;
   g_autoptr(GFileInfo) file_info = NULL;
-  g_autoptr(GbNewFilePopover) self = user_data;
+  g_autoptr(GbpNewFilePopover) self = user_data;
   g_autoptr(GError) error = NULL;
   GFileType file_type;
 
@@ -131,91 +130,99 @@ gb_new_file_popover__query_info_cb (GObject      *object,
 }
 
 static void
-gb_new_file_popover_check_exists (GbNewFilePopover *self,
-                                  GFile            *directory,
-                                  const gchar      *path)
+gbp_new_file_popover_check_exists (GbpNewFilePopover *self,
+                                   GFile             *directory,
+                                   const gchar       *path)
 {
   g_autoptr(GFile) child = NULL;
+  GCancellable *cancellable = NULL;
 
-  g_assert (GB_IS_NEW_FILE_POPOVER (self));
+  g_assert (GBP_IS_NEW_FILE_POPOVER (self));
   g_assert (!directory || G_IS_FILE (directory));
 
-  if (self->cancellable != NULL)
-    {
-      if (!g_cancellable_is_cancelled (self->cancellable))
-        g_cancellable_cancel (self->cancellable);
-      g_clear_object (&self->cancellable);
-    }
-
   gtk_label_set_label (self->message, NULL);
   gtk_widget_set_sensitive (GTK_WIDGET (self->button), FALSE);
 
   if (directory == NULL)
     return;
 
-  if (dzl_str_empty0 (path))
+  if (ide_str_empty0 (path))
     return;
 
   child = g_file_get_child (directory, path);
 
-  self->cancellable = g_cancellable_new ();
+  if (self->task)
+    cancellable = ide_task_get_cancellable (self->task);
 
   g_file_query_info_async (child,
                            G_FILE_ATTRIBUTE_STANDARD_TYPE,
                            G_FILE_QUERY_INFO_NONE,
                            G_PRIORITY_DEFAULT,
-                           self->cancellable,
-                           gb_new_file_popover__query_info_cb,
+                           cancellable,
+                           gbp_new_file_popover_query_info_cb,
                            g_object_ref (self));
 
 }
 
 static void
-gb_new_file_popover__entry_changed (GbNewFilePopover *self,
+gbp_new_file_popover_entry_changed (GbpNewFilePopover *self,
                                     GtkEntry         *entry)
 {
   const gchar *text;
 
-  g_assert (GB_IS_NEW_FILE_POPOVER (self));
+  g_assert (GBP_IS_NEW_FILE_POPOVER (self));
   g_assert (GTK_IS_ENTRY (entry));
 
   text = gtk_entry_get_text (entry);
 
   gtk_widget_set_sensitive (GTK_WIDGET (self->button), !dzl_str_empty0 (text));
 
-  gb_new_file_popover_check_exists (self, self->directory, text);
+  gbp_new_file_popover_check_exists (self, self->directory, text);
+}
+
+static void
+gbp_new_file_popover_closed (GtkPopover *popover)
+{
+  GbpNewFilePopover *self = (GbpNewFilePopover *)popover;
+  g_autoptr(IdeTask) task = NULL;
+
+  g_assert (GBP_IS_NEW_FILE_POPOVER (self));
+
+  if ((task = g_steal_pointer (&self->task)))
+    ide_task_return_new_error (task,
+                               G_IO_ERROR,
+                               G_IO_ERROR_CANCELLED,
+                               "The popover was closed");
 }
 
 static void
-gb_new_file_popover_finalize (GObject *object)
+gbp_new_file_popover_finalize (GObject *object)
 {
-  GbNewFilePopover *self = (GbNewFilePopover *)object;
+  GbpNewFilePopover *self = (GbpNewFilePopover *)object;
 
-  if (self->cancellable && !g_cancellable_is_cancelled (self->cancellable))
-    g_cancellable_cancel (self->cancellable);
+  g_assert (self->task == NULL);
 
-  g_clear_object (&self->cancellable);
   g_clear_object (&self->directory);
 
-  G_OBJECT_CLASS (gb_new_file_popover_parent_class)->finalize (object);
+  G_OBJECT_CLASS (gbp_new_file_popover_parent_class)->finalize (object);
 }
 
 static void
-gb_new_file_popover_get_property (GObject    *object,
-                                  guint       prop_id,
-                                  GValue     *value,
-                                  GParamSpec *pspec)
+gbp_new_file_popover_get_property (GObject    *object,
+                                   guint       prop_id,
+                                   GValue     *value,
+                                   GParamSpec *pspec)
 {
-  GbNewFilePopover *self = GB_NEW_FILE_POPOVER(object);
+  GbpNewFilePopover *self = GBP_NEW_FILE_POPOVER(object);
 
   switch (prop_id)
     {
     case PROP_DIRECTORY:
-      g_value_set_object (value, gb_new_file_popover_get_directory (self));
+      g_value_set_object (value, gbp_new_file_popover_get_directory (self));
       break;
 
     case PROP_FILE_TYPE:
-      g_value_set_enum (value, gb_new_file_popover_get_file_type (self));
+      g_value_set_enum (value, gbp_new_file_popover_get_file_type (self));
       break;
 
     default:
@@ -224,7 +231,7 @@ gb_new_file_popover_get_property (GObject    *object,
 }
 
 /**
- * gb_new_file_popover_set_property:
+ * gbp_new_file_popover_set_property:
  * @object: (in): a #GObject.
  * @prop_id: (in): The property identifier.
  * @value: (in): The given property.
@@ -235,21 +242,21 @@ gb_new_file_popover_get_property (GObject    *object,
  * Since: 3.32
  */
 static void
-gb_new_file_popover_set_property (GObject      *object,
-                                  guint         prop_id,
-                                  const GValue *value,
-                                  GParamSpec   *pspec)
+gbp_new_file_popover_set_property (GObject      *object,
+                                   guint         prop_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
 {
-  GbNewFilePopover *self = GB_NEW_FILE_POPOVER(object);
+  GbpNewFilePopover *self = GBP_NEW_FILE_POPOVER(object);
 
   switch (prop_id)
     {
     case PROP_DIRECTORY:
-      gb_new_file_popover_set_directory (self, g_value_get_object (value));
+      gbp_new_file_popover_set_directory (self, g_value_get_object (value));
       break;
 
     case PROP_FILE_TYPE:
-      gb_new_file_popover_set_file_type (self, g_value_get_enum (value));
+      gbp_new_file_popover_set_file_type (self, g_value_get_enum (value));
       break;
 
     default:
@@ -258,14 +265,17 @@ gb_new_file_popover_set_property (GObject      *object,
 }
 
 static void
-gb_new_file_popover_class_init (GbNewFilePopoverClass *klass)
+gbp_new_file_popover_class_init (GbpNewFilePopoverClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GtkPopoverClass *popover_class = GTK_POPOVER_CLASS (klass);
 
-  object_class->finalize = gb_new_file_popover_finalize;
-  object_class->get_property = gb_new_file_popover_get_property;
-  object_class->set_property = gb_new_file_popover_set_property;
+  object_class->finalize = gbp_new_file_popover_finalize;
+  object_class->get_property = gbp_new_file_popover_get_property;
+  object_class->set_property = gbp_new_file_popover_set_property;
+
+  popover_class->closed = gbp_new_file_popover_closed;
 
   properties [PROP_DIRECTORY] =
     g_param_spec_object ("directory",
@@ -282,28 +292,17 @@ gb_new_file_popover_class_init (GbNewFilePopoverClass *klass)
                        G_FILE_TYPE_REGULAR,
                        (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
-  g_object_class_install_properties (object_class, LAST_PROP, properties);
-
-  signals [CREATE_FILE] =
-    g_signal_new ("create-file",
-                  G_TYPE_FROM_CLASS (klass),
-                  G_SIGNAL_RUN_FIRST,
-                  0,
-                  NULL, NULL, NULL,
-                  G_TYPE_NONE,
-                  2,
-                  G_TYPE_FILE,
-                  G_TYPE_FILE_TYPE);
-
-  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/plugins/project-tree-plugin/gb-new-file-popover.ui");
-  gtk_widget_class_bind_template_child (widget_class, GbNewFilePopover, button);
-  gtk_widget_class_bind_template_child (widget_class, GbNewFilePopover, entry);
-  gtk_widget_class_bind_template_child (widget_class, GbNewFilePopover, message);
-  gtk_widget_class_bind_template_child (widget_class, GbNewFilePopover, title);
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/plugins/project-tree/gbp-new-file-popover.ui");
+  gtk_widget_class_bind_template_child (widget_class, GbpNewFilePopover, button);
+  gtk_widget_class_bind_template_child (widget_class, GbpNewFilePopover, entry);
+  gtk_widget_class_bind_template_child (widget_class, GbpNewFilePopover, message);
+  gtk_widget_class_bind_template_child (widget_class, GbpNewFilePopover, title);
 }
 
 static void
-gb_new_file_popover_init (GbNewFilePopover *self)
+gbp_new_file_popover_init (GbpNewFilePopover *self)
 {
   self->file_type = G_FILE_TYPE_REGULAR;
 
@@ -311,36 +310,36 @@ gb_new_file_popover_init (GbNewFilePopover *self)
 
   g_signal_connect_object (self->entry,
                            "activate",
-                           G_CALLBACK (gb_new_file_popover__entry_activate),
+                           G_CALLBACK (gbp_new_file_popover_entry_activate),
                            self,
                            G_CONNECT_SWAPPED);
 
   g_signal_connect_object (self->entry,
                            "changed",
-                           G_CALLBACK (gb_new_file_popover__entry_changed),
+                           G_CALLBACK (gbp_new_file_popover_entry_changed),
                            self,
                            G_CONNECT_SWAPPED);
 
   g_signal_connect_object (self->button,
                            "clicked",
-                           G_CALLBACK (gb_new_file_popover__button_clicked),
+                           G_CALLBACK (gbp_new_file_popover_button_clicked),
                            self,
                            G_CONNECT_SWAPPED);
 }
 
 GFileType
-gb_new_file_popover_get_file_type (GbNewFilePopover *self)
+gbp_new_file_popover_get_file_type (GbpNewFilePopover *self)
 {
-  g_return_val_if_fail (GB_IS_NEW_FILE_POPOVER (self), 0);
+  g_return_val_if_fail (GBP_IS_NEW_FILE_POPOVER (self), 0);
 
   return self->file_type;
 }
 
 void
-gb_new_file_popover_set_file_type (GbNewFilePopover *self,
-                                   GFileType         file_type)
+gbp_new_file_popover_set_file_type (GbpNewFilePopover *self,
+                                    GFileType          file_type)
 {
-  g_return_if_fail (GB_IS_NEW_FILE_POPOVER (self));
+  g_return_if_fail (GBP_IS_NEW_FILE_POPOVER (self));
   g_return_if_fail ((file_type == G_FILE_TYPE_REGULAR) ||
                     (file_type == G_FILE_TYPE_DIRECTORY));
 
@@ -358,10 +357,10 @@ gb_new_file_popover_set_file_type (GbNewFilePopover *self,
 }
 
 void
-gb_new_file_popover_set_directory (GbNewFilePopover *self,
-                                   GFile            *directory)
+gbp_new_file_popover_set_directory (GbpNewFilePopover *self,
+                                    GFile             *directory)
 {
-  g_return_if_fail (GB_IS_NEW_FILE_POPOVER (self));
+  g_return_if_fail (GBP_IS_NEW_FILE_POPOVER (self));
   g_return_if_fail (G_IS_FILE (directory));
 
   if (g_set_object (&self->directory, directory))
@@ -369,22 +368,54 @@ gb_new_file_popover_set_directory (GbNewFilePopover *self,
       const gchar *path;
 
       path = gtk_entry_get_text (self->entry);
-      gb_new_file_popover_check_exists (self, directory, path);
+      gbp_new_file_popover_check_exists (self, directory, path);
       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DIRECTORY]);
     }
 }
 
 /**
- * gb_new_file_popover_get_directory:
+ * gbp_new_file_popover_get_directory:
  *
  * Returns: (transfer none) (nullable): a #GFile or %NULL.
  *
  * Since: 3.32
  */
 GFile *
-gb_new_file_popover_get_directory (GbNewFilePopover *self)
+gbp_new_file_popover_get_directory (GbpNewFilePopover *self)
 {
-  g_return_val_if_fail (GB_IS_NEW_FILE_POPOVER (self), NULL);
+  g_return_val_if_fail (GBP_IS_NEW_FILE_POPOVER (self), NULL);
 
   return self->directory;
 }
+
+void
+gbp_new_file_popover_display_async (GbpNewFilePopover   *self,
+                                    IdeTree             *tree,
+                                    IdeTreeNode         *node,
+                                    GCancellable        *cancellable,
+                                    GAsyncReadyCallback  callback,
+                                    gpointer             user_data)
+{
+  g_return_if_fail (GBP_IS_NEW_FILE_POPOVER (self));
+  g_return_if_fail (IDE_IS_TREE (tree));
+  g_return_if_fail (IDE_IS_TREE_NODE (node));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_return_if_fail (self->task == NULL);
+
+  self->task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (self->task, gbp_new_file_popover_display_async);
+
+  ide_tree_expand_node (tree, node);
+  ide_tree_show_popover_at_node (tree, node, GTK_POPOVER (self));
+}
+
+GFile *
+gbp_new_file_popover_display_finish (GbpNewFilePopover  *self,
+                                     GAsyncResult       *result,
+                                     GError            **error)
+{
+  g_return_val_if_fail (GBP_IS_NEW_FILE_POPOVER (self), NULL);
+  g_return_val_if_fail (IDE_IS_TASK (result), NULL);
+
+  return ide_task_propagate_pointer (IDE_TASK (result), error);
+}
diff --git a/src/plugins/project-tree/gbp-new-file-popover.h b/src/plugins/project-tree/gbp-new-file-popover.h
new file mode 100644
index 000000000..1a7b51aff
--- /dev/null
+++ b/src/plugins/project-tree/gbp-new-file-popover.h
@@ -0,0 +1,48 @@
+/* gbp-new-file-popover.h
+ *
+ * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ *
+ * 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
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <libide-tree.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_NEW_FILE_POPOVER (gbp_new_file_popover_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpNewFilePopover, gbp_new_file_popover, GBP, NEW_FILE_POPOVER, GtkPopover)
+
+GFileType  gbp_new_file_popover_get_file_type  (GbpNewFilePopover    *self);
+void       gbp_new_file_popover_set_file_type  (GbpNewFilePopover    *self,
+                                                GFileType             file_type);
+void       gbp_new_file_popover_set_directory  (GbpNewFilePopover    *self,
+                                                GFile                *directory);
+GFile     *gbp_new_file_popover_get_directory  (GbpNewFilePopover    *self);
+void       gbp_new_file_popover_display_async  (GbpNewFilePopover    *self,
+                                                IdeTree              *tree,
+                                                IdeTreeNode          *node,
+                                                GCancellable         *cancellable,
+                                                GAsyncReadyCallback   callback,
+                                                gpointer              user_data);
+GFile     *gbp_new_file_popover_display_finish (GbpNewFilePopover    *self,
+                                                GAsyncResult         *result,
+                                                GError              **error);
+
+G_END_DECLS
diff --git a/src/plugins/project-tree/gb-new-file-popover.ui 
b/src/plugins/project-tree/gbp-new-file-popover.ui
similarity index 97%
rename from src/plugins/project-tree/gb-new-file-popover.ui
rename to src/plugins/project-tree/gbp-new-file-popover.ui
index 6e690578b..5cbce4915 100644
--- a/src/plugins/project-tree/gb-new-file-popover.ui
+++ b/src/plugins/project-tree/gbp-new-file-popover.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <!-- interface-requires gtk+ 3.16 -->
-  <template class="GbNewFilePopover" parent="GtkPopover">
+  <template class="GbpNewFilePopover" parent="GtkPopover">
     <child>
       <object class="GtkBox">
         <property name="border-width">12</property>
diff --git a/src/plugins/project-tree/gbp-project-tree-addin.c 
b/src/plugins/project-tree/gbp-project-tree-addin.c
new file mode 100644
index 000000000..d3d0a7f48
--- /dev/null
+++ b/src/plugins/project-tree/gbp-project-tree-addin.c
@@ -0,0 +1,896 @@
+/* gbp-project-tree-addin.c
+ *
+ * Copyright 2018-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 "gbp-project-tree-addin"
+
+#include "config.h"
+
+#include <dazzle.h>
+#include <glib/gi18n.h>
+#include <libide-gui.h>
+#include <libide-projects.h>
+#include <libide-tree.h>
+#include <libide-vcs.h>
+
+#include "gbp-project-tree-addin.h"
+
+struct _GbpProjectTreeAddin
+{
+  GObject       parent_instance;
+
+  IdeTree      *tree;
+  IdeTreeModel *model;
+  GSettings    *settings;
+
+  guint         sort_directories_first : 1;
+  guint         show_ignored_files : 1;
+};
+
+typedef struct
+{
+  GFile       *file;
+  IdeTreeNode *node;
+} FindFileNode;
+
+static gboolean
+project_file_is_ignored (IdeProjectFile *project_file,
+                         IdeVcs         *vcs)
+{
+  g_autoptr(GFile) file = NULL;
+
+  g_assert (IDE_IS_PROJECT_FILE (project_file));
+
+  file = ide_project_file_ref_file (project_file);
+
+  return ide_vcs_is_ignored (vcs, file, NULL);
+}
+
+static gint
+compare_files (gconstpointer a,
+               gconstpointer b,
+               gpointer      user_data)
+{
+  GbpProjectTreeAddin *self = user_data;
+  IdeProjectFile *file_a = *(IdeProjectFile **)a;
+  IdeProjectFile *file_b = *(IdeProjectFile **)b;
+
+  g_assert (IDE_IS_PROJECT_FILE (file_a));
+  g_assert (IDE_IS_PROJECT_FILE (file_b));
+
+  if (self->sort_directories_first)
+    return ide_project_file_compare_directories_first (file_a, file_b);
+  else
+    return ide_project_file_compare (file_a, file_b);
+}
+
+static IdeTreeNode *
+create_file_node (IdeProjectFile *file)
+{
+  IdeTreeNode *child;
+
+  g_assert (IDE_IS_PROJECT_FILE (file));
+
+  child = ide_tree_node_new ();
+  ide_tree_node_set_item (child, G_OBJECT (file));
+  ide_tree_node_set_display_name (child, ide_project_file_get_display_name (file));
+  ide_tree_node_set_icon (child, ide_project_file_get_symbolic_icon (file));
+  g_object_set (child, "destroy-item", TRUE, NULL);
+
+  if (ide_project_file_is_directory (file))
+    {
+      ide_tree_node_set_children_possible (child, TRUE);
+      ide_tree_node_set_expanded_icon_name (child, "folder-open-symbolic");
+    }
+
+  return g_steal_pointer (&child);
+}
+
+static void
+gbp_project_tree_addin_file_list_children_cb (GObject      *object,
+                                              GAsyncResult *result,
+                                              gpointer      user_data)
+{
+  IdeProjectFile *project_file = (IdeProjectFile *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GPtrArray) children = NULL;
+  g_autoptr(GError) error = NULL;
+  GbpProjectTreeAddin *self;
+  IdeTreeNode *last = NULL;
+  IdeTreeNode *node;
+  IdeTreeNode *root;
+  IdeContext *context;
+  IdeVcs *vcs;
+
+  g_assert (IDE_IS_PROJECT_FILE (project_file));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  if (!(children = ide_project_file_list_children_finish (project_file, result, &error)))
+    {
+      ide_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  IDE_PTR_ARRAY_SET_FREE_FUNC (children, g_object_unref);
+
+  self = ide_task_get_source_object (task);
+  node = ide_task_get_task_data (task);
+  root = ide_tree_node_get_root (node);
+  context = ide_tree_node_get_item (root);
+  vcs = ide_vcs_from_context (context);
+
+  g_assert (GBP_IS_PROJECT_TREE_ADDIN (self));
+  g_assert (IDE_IS_TREE_NODE (node));
+
+  g_ptr_array_sort_with_data (children, compare_files, self);
+
+  for (guint i = 0; i < children->len; i++)
+    {
+      IdeProjectFile *file = g_ptr_array_index (children, i);
+      g_autoptr(IdeTreeNode) child = NULL;
+
+      if (!self->show_ignored_files)
+        {
+          if (project_file_is_ignored (file, vcs))
+            continue;
+        }
+
+      child = create_file_node (file);
+
+      if (last == NULL)
+        ide_tree_node_append (node, child);
+      else
+        ide_tree_node_insert_after (last, child);
+
+      last = child;
+    }
+
+  ide_task_return_boolean (task, TRUE);
+}
+
+static void
+gbp_project_tree_addin_build_children_async (IdeTreeAddin        *addin,
+                                             IdeTreeNode         *node,
+                                             GCancellable        *cancellable,
+                                             GAsyncReadyCallback  callback,
+                                             gpointer             user_data)
+{
+  g_autoptr(IdeTask) task = NULL;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_TREE_ADDIN (addin));
+  g_assert (IDE_IS_TREE_NODE (node));
+
+  task = ide_task_new (addin, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, gbp_project_tree_addin_build_children_async);
+  ide_task_set_task_data (task, g_object_ref (node), g_object_unref);
+
+  if (ide_tree_node_holds (node, IDE_TYPE_CONTEXT))
+    {
+      IdeContext *context = ide_tree_node_get_item (node);
+      g_autoptr(IdeTreeNode) files = NULL;
+      g_autoptr(IdeTreeNode) targets = NULL;
+      //g_autoptr(IdeTreeNode) tests = NULL;
+      g_autoptr(IdeProjectFile) root_file = NULL;
+      g_autoptr(GFile) workdir = ide_context_ref_workdir (context);
+      g_autoptr(GFile) parent = g_file_get_parent (workdir);
+      g_autoptr(GFileInfo) info = NULL;
+      g_autofree gchar *name = NULL;
+
+#if 0
+      tests = g_object_new (IDE_TYPE_TREE_NODE,
+                            "icon-name", "builder-unit-tests-symbolic",
+                            "item", NULL,
+                            "display-name", _("Unit Tests"),
+                            "children-possible", TRUE,
+                            NULL);
+      ide_tree_node_append (node, tests);
+#endif
+
+      info = g_file_info_new ();
+      name = g_file_get_basename (workdir);
+      g_file_info_set_name (info, name);
+      g_file_info_set_display_name (info, name);
+      g_file_info_set_content_type (info, "inode/directory");
+      g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
+      g_file_info_set_is_symlink (info, FALSE);
+      root_file = ide_project_file_new (parent, info);
+      files = create_file_node (root_file);
+      ide_tree_node_set_display_name (files, _("Files"));
+      ide_tree_node_set_icon_name (files, "view-list-symbolic");
+      ide_tree_node_set_expanded_icon_name (files, "view-list-symbolic");
+      ide_tree_node_append (node, files);
+    }
+  else if (ide_tree_node_holds (node, IDE_TYPE_PROJECT_FILE))
+    {
+      IdeProjectFile *project_file = ide_tree_node_get_item (node);
+
+      ide_project_file_list_children_async (project_file,
+                                            cancellable,
+                                            gbp_project_tree_addin_file_list_children_cb,
+                                            g_steal_pointer (&task));
+
+      return;
+    }
+
+  ide_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+gbp_project_tree_addin_build_children_finish (IdeTreeAddin  *addin,
+                                              GAsyncResult  *result,
+                                              GError       **error)
+{
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_TREE_ADDIN (addin));
+  g_assert (IDE_IS_TASK (result));
+
+  return ide_task_propagate_boolean (IDE_TASK (result), error);
+}
+
+static gboolean
+gbp_project_tree_addin_node_activated (IdeTreeAddin *addin,
+                                       IdeTree      *tree,
+                                       IdeTreeNode  *node)
+{
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_PROJECT_TREE_ADDIN (addin));
+  g_assert (IDE_IS_TREE_NODE (node));
+
+  if (ide_tree_node_holds (node, IDE_TYPE_PROJECT_FILE))
+    {
+      IdeProjectFile *project_file = ide_tree_node_get_item (node);
+      g_autoptr(GFile) file = NULL;
+      IdeWorkbench *workbench;
+
+      /* Ignore directories, we want to expand them */
+      if (ide_project_file_is_directory (project_file))
+        return FALSE;
+
+      file = ide_project_file_ref_file (project_file);
+      workbench = ide_widget_get_workbench (GTK_WIDGET (tree));
+
+      ide_workbench_open_async (workbench, file, NULL, 0, NULL, NULL, NULL);
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static IdeTreeNodeVisit
+traverse_cb (IdeTreeNode *node,
+             gpointer     user_data)
+{
+  FindFileNode *find = user_data;
+
+  if (ide_tree_node_holds (node, IDE_TYPE_PROJECT_FILE))
+    {
+      IdeProjectFile *project_file = ide_tree_node_get_item (node);
+      g_autoptr(GFile) file = ide_project_file_ref_file (project_file);
+
+      if (g_file_equal (find->file, file))
+        {
+          find->node = node;
+          return IDE_TREE_NODE_VISIT_BREAK;
+        }
+
+      if (g_file_has_prefix (find->file, file))
+        return IDE_TREE_NODE_VISIT_CHILDREN;
+    }
+
+  return IDE_TREE_NODE_VISIT_CONTINUE;
+}
+
+static IdeTreeNode *
+find_file_node (IdeTree *tree,
+                GFile   *file)
+{
+  GtkTreeModel *model;
+  IdeTreeNode *root;
+  FindFileNode find;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_TREE (tree));
+  g_assert (G_IS_FILE (file));
+
+  model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree));
+  root = ide_tree_model_get_root (IDE_TREE_MODEL (model));
+
+  find.file = file;
+  find.node = NULL;
+
+  ide_tree_node_traverse (root,
+                          G_PRE_ORDER,
+                          G_TRAVERSE_ALL,
+                          -1,
+                          traverse_cb,
+                          &find);
+
+  return find.node;
+}
+
+static GList *
+collect_files (GFile *file,
+               GFile *stop_at)
+{
+  g_autoptr(GFile) copy = g_object_ref (file);
+  GList *list = NULL;
+
+  g_assert (g_file_has_prefix (file, stop_at));
+
+  while (!g_file_equal (copy, stop_at))
+    {
+      GFile *stolen = g_steal_pointer (&copy);
+
+      list = g_list_prepend (list, stolen);
+      copy = g_file_get_parent (stolen);
+    }
+
+  return g_steal_pointer (&list);
+}
+
+static void
+gbp_project_tree_addin_add_file (GbpProjectTreeAddin *self,
+                                 GFile               *file)
+{
+  g_autolist(GFile) list = NULL;
+  g_autoptr(GFile) workdir = NULL;
+  IdeTreeNode *parent = NULL;
+  IdeContext *context;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_PROJECT_TREE_ADDIN (self));
+  g_assert (G_IS_FILE (file));
+
+#ifdef IDE_ENABLE_TRACE
+  {
+    g_autofree gchar *uri = g_file_get_uri (file);
+    IDE_TRACE_MSG ("Adding file to tree \"%s\"", uri);
+  }
+#endif
+
+  context = ide_widget_get_context (GTK_WIDGET (self->tree));
+  workdir = ide_context_ref_workdir (context);
+
+  if (!g_file_has_prefix (file, workdir))
+    return;
+
+  list = collect_files (file, workdir);
+
+  for (const GList *iter = list; iter; iter = iter->next)
+    {
+      GFile *item = iter->data;
+      g_autoptr(IdeProjectFile) project_file = NULL;
+      g_autoptr(GFileInfo) info = NULL;
+      g_autoptr(IdeTreeNode) node = NULL;
+      g_autoptr(GFile) directory = NULL;
+
+      g_assert (G_IS_FILE (item));
+
+      if ((parent = find_file_node (self->tree, item)))
+        {
+          if (!ide_tree_node_expanded (self->tree, parent))
+            IDE_EXIT;
+
+          continue;
+        }
+
+      directory = g_file_get_parent (item);
+      parent = find_file_node (self->tree, directory);
+
+      info = g_file_query_info (item,
+                                IDE_PROJECT_FILE_ATTRIBUTES,
+                                G_FILE_QUERY_INFO_NONE,
+                                NULL, NULL);
+
+      if (info == NULL)
+        IDE_EXIT;
+
+      project_file = ide_project_file_new (directory, info);
+      node = create_file_node (project_file);
+
+      /* TODO: Sort item */
+      ide_tree_node_append (parent, node);
+    }
+
+  IDE_EXIT;
+}
+
+static void
+gbp_project_tree_addin_remove_file (GbpProjectTreeAddin *self,
+                                    GFile               *file)
+{
+  IdeTreeNode *selected;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_PROJECT_TREE_ADDIN (self));
+  g_assert (G_IS_FILE (file));
+
+#ifdef IDE_ENABLE_TRACE
+  {
+    g_autofree gchar *uri = g_file_get_uri (file);
+    IDE_TRACE_MSG ("Removing file from tree \"%s\"", uri);
+  }
+#endif
+
+  if ((selected = find_file_node (self->tree, file)))
+    ide_tree_node_remove (ide_tree_node_get_parent (selected), selected);
+
+  IDE_EXIT;
+}
+
+static void
+gbp_project_tree_addin_changed_cb (GbpProjectTreeAddin *self,
+                                   GFile               *file,
+                                   GFile               *other_file,
+                                   GFileMonitorEvent    event,
+                                   IdeVcsMonitor       *monitor)
+{
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_PROJECT_TREE_ADDIN (self));
+  g_assert (G_IS_FILE (file));
+  g_assert (!other_file || G_IS_FILE (other_file));
+  g_assert (IDE_IS_VCS_MONITOR (monitor));
+
+  if (event == G_FILE_MONITOR_EVENT_CREATED)
+    gbp_project_tree_addin_add_file (self, file);
+  else if (event == G_FILE_MONITOR_EVENT_DELETED)
+    gbp_project_tree_addin_remove_file (self, file);
+}
+
+static void
+gbp_project_tree_addin_reloaded_cb (GbpProjectTreeAddin *self,
+                                    IdeVcsMonitor       *monitor)
+{
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_PROJECT_TREE_ADDIN (self));
+  g_assert (IDE_IS_VCS_MONITOR (monitor));
+
+  gtk_widget_queue_resize (GTK_WIDGET (self->tree));
+}
+
+static void
+gbp_project_tree_addin_load (IdeTreeAddin *addin,
+                             IdeTree      *tree,
+                             IdeTreeModel *model)
+{
+  static const GtkTargetEntry drag_targets[] = {
+    { (gchar *)"GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, 0 },
+    { (gchar *)"text/uri-list", 0, 0 },
+  };
+
+  GbpProjectTreeAddin *self = (GbpProjectTreeAddin *)addin;
+  IdeVcsMonitor *monitor;
+  IdeWorkbench *workbench;
+
+  g_assert (GBP_IS_PROJECT_TREE_ADDIN (self));
+  g_assert (IDE_IS_TREE_MODEL (model));
+
+  self->tree = tree;
+  self->model = model;
+
+  workbench = ide_widget_get_workbench (GTK_WIDGET (tree));
+  monitor = ide_workbench_get_vcs_monitor (workbench);
+
+  g_signal_connect_object (monitor,
+                           "changed",
+                           G_CALLBACK (gbp_project_tree_addin_changed_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (monitor,
+                           "reloaded",
+                           G_CALLBACK (gbp_project_tree_addin_reloaded_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (tree),
+                                          GDK_BUTTON1_MASK,
+                                          drag_targets, G_N_ELEMENTS (drag_targets),
+                                          GDK_ACTION_COPY | GDK_ACTION_MOVE);
+  gtk_tree_view_enable_model_drag_dest (GTK_TREE_VIEW (tree),
+                                        drag_targets, G_N_ELEMENTS (drag_targets),
+                                        GDK_ACTION_COPY | GDK_ACTION_MOVE);
+}
+
+static void
+gbp_project_tree_addin_unload (IdeTreeAddin *addin,
+                               IdeTree      *tree,
+                               IdeTreeModel *model)
+{
+  GbpProjectTreeAddin *self = (GbpProjectTreeAddin *)addin;
+
+  g_assert (GBP_IS_PROJECT_TREE_ADDIN (self));
+  g_assert (IDE_IS_TREE_MODEL (model));
+
+  self->tree = NULL;
+  self->model = NULL;
+}
+
+static gboolean
+gbp_project_tree_addin_node_draggable (IdeTreeAddin *addin,
+                                       IdeTreeNode  *node)
+{
+  return ide_tree_node_holds (node, IDE_TYPE_PROJECT_FILE);
+}
+
+static gboolean
+gbp_project_tree_addin_node_droppable (IdeTreeAddin     *addin,
+                                       IdeTreeNode      *drag_node,
+                                       IdeTreeNode      *drop_node,
+                                       GtkSelectionData *selection)
+{
+  IdeProjectFile *drop_file = NULL;
+  g_auto(GStrv) uris = NULL;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_PROJECT_TREE_ADDIN (addin));
+  g_assert (!drag_node || IDE_IS_TREE_NODE (drag_node));
+  g_assert (!drop_node || IDE_IS_TREE_NODE (drop_node));
+
+  /* Must drop on a file */
+  if (drop_node == NULL ||
+      !ide_tree_node_holds (drop_node, IDE_TYPE_PROJECT_FILE))
+    return FALSE;
+
+  /* The drop file must be a directory */
+  drop_file = ide_tree_node_get_item (drop_node);
+  if (!ide_project_file_is_directory (drop_file))
+    return FALSE;
+
+  /* We need a uri list or file node */
+  uris = gtk_selection_data_get_uris (selection);
+  if ((uris == NULL || uris[0] == NULL) && drag_node == NULL)
+    return FALSE;
+
+  /* If we have a drag node, make sure it's a file */
+  if (drag_node != NULL &&
+      !ide_tree_node_holds (drop_node, IDE_TYPE_PROJECT_FILE))
+    return FALSE;
+
+  return TRUE;
+}
+
+static void
+gbp_project_tree_addin_notify_progress_cb (DzlFileTransfer *transfer,
+                                           GParamSpec      *pspec,
+                                           IdeNotification *notif)
+{
+  g_autofree gchar *body = NULL;
+  DzlFileTransferStat stbuf;
+  gchar count[16];
+  gchar total[16];
+  gdouble progress;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (DZL_IS_FILE_TRANSFER (transfer));
+  g_assert (IDE_IS_NOTIFICATION (notif));
+
+  dzl_file_transfer_stat (transfer, &stbuf);
+
+  progress = dzl_file_transfer_get_progress (transfer);
+  ide_notification_set_progress (notif, progress);
+
+  g_snprintf (count, sizeof count, "%"G_GINT64_FORMAT, stbuf.n_files);
+  g_snprintf (total, sizeof total, "%"G_GINT64_FORMAT, stbuf.n_files_total);
+
+  if (stbuf.n_files_total == 1)
+    body = g_strdup_printf (_("Copying 1 file"));
+  else
+    /* translators: first %s is replaced with completed number of files, second %s with total number of 
files */
+    body = g_strdup_printf (_("Copying %s of %s files"), count, total);
+
+  ide_notification_set_body (notif, body);
+}
+
+static void
+gbp_project_tree_addin_transfer_cb (GObject      *object,
+                                    GAsyncResult *result,
+                                    gpointer      user_data)
+{
+  DzlFileTransfer *transfer = (DzlFileTransfer *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  GbpProjectTreeAddin *self;
+  IdeNotification *notif;
+  DzlFileTransferStat stbuf;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (DZL_IS_FILE_TRANSFER (transfer));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  self = ide_task_get_source_object (task);
+  notif = ide_task_get_task_data (task);
+
+  g_assert (GBP_IS_PROJECT_TREE_ADDIN (self));
+  g_assert (notif != NULL);
+  g_assert (IDE_IS_NOTIFICATION (notif));
+
+  gbp_project_tree_addin_notify_progress_cb (transfer, NULL, notif);
+  ide_notification_set_progress (notif, 1.0);
+
+  if (!dzl_file_transfer_execute_finish (transfer, result, &error))
+    {
+      ide_notification_set_title (notif, _("Failed to copy files"));
+      ide_notification_set_body (notif, error->message);
+      ide_task_return_error (task, g_steal_pointer (&error));
+    }
+  else
+    {
+      GPtrArray *sources;
+
+      dzl_file_transfer_stat (transfer, &stbuf);
+
+      ide_notification_set_title (notif, _("Files copied"));
+
+      if (stbuf.n_files_total == 1)
+        {
+          ide_notification_set_body (notif, _("Copied 1 file"));
+        }
+      else
+        {
+          g_autofree gchar *format = NULL;
+          gchar count[16];
+
+          g_snprintf (count, sizeof count, "%"G_GINT64_FORMAT, stbuf.n_files_total);
+          format = g_strdup_printf (_("Copied %s files"), count);
+          ide_notification_set_body (notif, format);
+        }
+
+      sources = g_object_get_data (G_OBJECT (task), "SOURCE_FILES");
+
+      if (sources != NULL)
+        {
+          IdeContext *context;
+          IdeProject *project;
+
+          /*
+           * We avoid deleting files here and instead just trash the
+           * existing files to help reduce any chance that we delete
+           * user data.
+           *
+           * Also, this will only trash files that are within our
+           * project directory. Currently, I'm considering that a
+           * feature, but when I trust file-deletion more, we can
+           * open it up in IdeProject.
+           */
+
+          context = ide_object_get_context (IDE_OBJECT (self->model));
+          project = ide_project_from_context (context);
+
+          for (guint i = 0; i < sources->len; i++)
+            {
+              GFile *source = g_ptr_array_index (sources, i);
+
+              g_assert (G_IS_FILE (source));
+
+              ide_project_trash_file_async (project, source, NULL, NULL, NULL);
+            }
+        }
+
+      ide_task_return_boolean (task, TRUE);
+    }
+
+  ide_notification_withdraw_in_seconds (notif, -1);
+
+  IDE_EXIT;
+}
+
+static void
+gbp_project_tree_addin_node_dropped_async (IdeTreeAddin        *addin,
+                                           IdeTreeNode         *drag_node,
+                                           IdeTreeNode         *drop_node,
+                                           GtkSelectionData    *selection,
+                                           GdkDragAction        actions,
+                                           GCancellable        *cancellable,
+                                           GAsyncReadyCallback  callback,
+                                           gpointer             user_data)
+{
+  GbpProjectTreeAddin *self = (GbpProjectTreeAddin *)addin;
+  g_autoptr(IdeTask) task = NULL;
+  g_autoptr(DzlFileTransfer) transfer = NULL;
+  g_autoptr(GFile) src_file = NULL;
+  g_autoptr(GFile) dst_dir = NULL;
+  g_autoptr(IdeNotification) notif = NULL;
+  g_autoptr(GPtrArray) srcs = NULL;
+  g_auto(GStrv) uris = NULL;
+  IdeProjectFile *drag_file;
+  IdeProjectFile *drop_file;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_PROJECT_TREE_ADDIN (self));
+  g_assert (!drag_node || IDE_IS_TREE_NODE (drag_node));
+  g_assert (!drop_node || IDE_IS_TREE_NODE (drop_node));
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, gbp_project_tree_addin_node_dropped_async);
+
+  if (!gbp_project_tree_addin_node_droppable (addin, drag_node, drop_node, selection))
+    {
+      ide_task_return_boolean (task, TRUE);
+      IDE_EXIT;
+    }
+
+  srcs = g_ptr_array_new_with_free_func (g_object_unref);
+  uris = gtk_selection_data_get_uris (selection);
+
+  if (uris != NULL)
+    {
+      for (guint i = 0; uris[i]; i++)
+        g_ptr_array_add (srcs, g_file_new_for_uri (uris[i]));
+    }
+
+  drop_file = ide_tree_node_get_item (drop_node);
+  g_assert (drop_file != NULL);
+  g_assert (ide_project_file_is_directory (drop_file));
+
+  if (drag_node != NULL)
+    {
+      drag_file = ide_tree_node_get_item (drag_node);
+      src_file = ide_project_file_ref_file (drag_file);
+      g_assert (G_IS_FILE (src_file));
+      g_ptr_array_add (srcs, g_object_ref (src_file));
+    }
+
+  dst_dir = ide_project_file_ref_file (drop_file);
+  g_assert (G_IS_FILE (dst_dir));
+
+  transfer = dzl_file_transfer_new ();
+  dzl_file_transfer_set_flags (transfer, DZL_FILE_TRANSFER_FLAGS_NONE);
+  g_signal_connect_object (transfer,
+                           "notify::progress",
+                           G_CALLBACK (gbp_project_tree_addin_notify_progress_cb),
+                           notif,
+                           0);
+
+  for (guint i = 0; i < srcs->len; i++)
+    {
+      GFile *source = g_ptr_array_index (srcs, i);
+      g_autofree gchar *name = NULL;
+      g_autoptr(GFile) dst_file = NULL;
+
+      name = g_file_get_basename (source);
+      g_assert (name != NULL);
+
+      dst_file = g_file_get_child (dst_dir, name);
+      g_assert (G_IS_FILE (dst_file));
+
+      if (srcs->len == 1 && g_file_equal (source, dst_file))
+        {
+          ide_task_return_boolean (task, TRUE);
+          IDE_EXIT;
+        }
+
+      dzl_file_transfer_add (transfer, source, dst_file);
+    }
+
+  if (actions == GDK_ACTION_MOVE)
+    g_object_set_data_full (G_OBJECT (task),
+                            "SOURCE_FILES",
+                            g_steal_pointer (&srcs),
+                            (GDestroyNotify)g_ptr_array_unref);
+
+  notif = ide_notification_new ();
+  ide_notification_set_title (notif, _("Copying files…"));
+  ide_notification_set_body (notif, _("Files will be copied in a moment"));
+  ide_notification_set_has_progress (notif, TRUE);
+  ide_notification_attach (notif, IDE_OBJECT (self->model));
+  ide_task_set_task_data (task, g_object_ref (notif), g_object_unref);
+
+  dzl_file_transfer_execute_async (transfer,
+                                   G_PRIORITY_DEFAULT,
+                                   cancellable,
+                                   gbp_project_tree_addin_transfer_cb,
+                                   g_steal_pointer (&task));
+
+  IDE_EXIT;
+}
+
+static gboolean
+gbp_project_tree_addin_node_dropped_finish (IdeTreeAddin  *addin,
+                                            GAsyncResult  *result,
+                                            GError       **error)
+{
+  gboolean ret;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_PROJECT_TREE_ADDIN (addin));
+  g_assert (IDE_IS_TASK (result));
+
+  ret = ide_task_propagate_boolean (IDE_TASK (result), error);
+
+  IDE_RETURN (ret);
+}
+
+static void
+tree_addin_iface_init (IdeTreeAddinInterface *iface)
+{
+  iface->load = gbp_project_tree_addin_load;
+  iface->unload = gbp_project_tree_addin_unload;
+  iface->build_children_async = gbp_project_tree_addin_build_children_async;
+  iface->build_children_finish = gbp_project_tree_addin_build_children_finish;
+  iface->node_activated = gbp_project_tree_addin_node_activated;
+  iface->node_draggable = gbp_project_tree_addin_node_draggable;
+  iface->node_droppable = gbp_project_tree_addin_node_droppable;
+  iface->node_dropped_async = gbp_project_tree_addin_node_dropped_async;
+  iface->node_dropped_finish = gbp_project_tree_addin_node_dropped_finish;
+}
+
+static void
+gbp_project_tree_addin_settings_changed (GbpProjectTreeAddin *self,
+                                         const gchar         *key,
+                                         GSettings           *settings)
+{
+  g_assert (GBP_IS_PROJECT_TREE_ADDIN (self));
+  g_assert (G_IS_SETTINGS (settings));
+
+  self->sort_directories_first = g_settings_get_boolean (self->settings, "sort-directories-first");
+  self->show_ignored_files = g_settings_get_boolean (self->settings, "show-ignored-files");
+
+  if (self->model != NULL)
+    ide_tree_model_invalidate (self->model, NULL);
+}
+
+G_DEFINE_TYPE_WITH_CODE (GbpProjectTreeAddin, gbp_project_tree_addin, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (IDE_TYPE_TREE_ADDIN, tree_addin_iface_init))
+
+static void
+gbp_project_tree_addin_dispose (GObject *object)
+{
+  GbpProjectTreeAddin *self = (GbpProjectTreeAddin *)object;
+
+  g_clear_object (&self->settings);
+
+  G_OBJECT_CLASS (gbp_project_tree_addin_parent_class)->dispose (object);
+}
+
+static void
+gbp_project_tree_addin_class_init (GbpProjectTreeAddinClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = gbp_project_tree_addin_dispose;
+}
+
+static void
+gbp_project_tree_addin_init (GbpProjectTreeAddin *self)
+{
+  self->settings = g_settings_new ("org.gnome.builder.project-tree");
+
+  g_signal_connect_object (self->settings,
+                           "changed",
+                           G_CALLBACK (gbp_project_tree_addin_settings_changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  gbp_project_tree_addin_settings_changed (self, NULL, self->settings);
+}
diff --git a/src/plugins/project-tree/gb-project-tree-addin.h 
b/src/plugins/project-tree/gbp-project-tree-addin.h
similarity index 74%
rename from src/plugins/project-tree/gb-project-tree-addin.h
rename to src/plugins/project-tree/gbp-project-tree-addin.h
index 0ff3faf8d..525b8fba3 100644
--- a/src/plugins/project-tree/gb-project-tree-addin.h
+++ b/src/plugins/project-tree/gbp-project-tree-addin.h
@@ -1,6 +1,6 @@
-/* gb-project-tree-addin.h
+/* gbp-project-tree-addin.h
  *
- * Copyright 2015-2019 Christian Hergert <chergert redhat com>
+ * Copyright 2018-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
@@ -24,8 +24,8 @@
 
 G_BEGIN_DECLS
 
-#define GB_TYPE_PROJECT_TREE_ADDIN (gb_project_tree_addin_get_type())
+#define GBP_TYPE_PROJECT_TREE_ADDIN (gbp_project_tree_addin_get_type())
 
-G_DECLARE_FINAL_TYPE (GbProjectTreeAddin, gb_project_tree_addin, GB, PROJECT_TREE_ADDIN, GObject)
+G_DECLARE_FINAL_TYPE (GbpProjectTreeAddin, gbp_project_tree_addin, GBP, PROJECT_TREE_ADDIN, GObject)
 
 G_END_DECLS
diff --git a/src/plugins/project-tree/gbp-project-tree-pane-actions.c 
b/src/plugins/project-tree/gbp-project-tree-pane-actions.c
new file mode 100644
index 000000000..acb795a0a
--- /dev/null
+++ b/src/plugins/project-tree/gbp-project-tree-pane-actions.c
@@ -0,0 +1,634 @@
+/* gbp-project-tree-pane-actions.c
+ *
+ * Copyright 2018-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 "gbp-project-tree-pane-actions"
+
+#include "config.h"
+
+#include <libide-editor.h>
+#include <libide-projects.h>
+#include <vte/vte.h>
+
+#include "gbp-project-tree-private.h"
+#include "gbp-rename-file-popover.h"
+#include "gbp-new-file-popover.h"
+
+typedef struct
+{
+  IdeTreeNode *node;
+  GFile       *file;
+  GFileType    file_type;
+  guint        needs_collapse : 1;
+} NewState;
+
+static void
+new_state_free (NewState *state)
+{
+  g_clear_object (&state->node);
+  g_clear_object (&state->file);
+  g_slice_free (NewState, state);
+}
+
+static void
+new_action_completed_cb (GObject      *object,
+                         GAsyncResult *result,
+                         gpointer      user_data)
+{
+  GbpProjectTreePane *self = (GbpProjectTreePane *)object;
+  NewState *state;
+
+  g_assert (GBP_IS_PROJECT_TREE_PANE (self));
+  g_assert (IDE_IS_TASK (result));
+
+  state = ide_task_get_task_data (IDE_TASK (result));
+  g_assert (state != NULL);
+  g_assert (IDE_IS_TREE_NODE (state->node));
+
+  if (state->needs_collapse)
+    ide_tree_collapse_node (self->tree, state->node);
+
+  /* Open the file if we created a regular file */
+  if (state->file_type == G_FILE_TYPE_REGULAR)
+    {
+      IdeWorkbench *workbench;
+
+      if (!(workbench = ide_widget_get_workbench (GTK_WIDGET (self->tree))))
+        return;
+
+      if (state->file != NULL)
+        ide_workbench_open_async (workbench, state->file, "editor", 0, NULL, NULL, NULL);
+    }
+}
+
+static void
+gbp_project_tree_pane_actions_mkdir_cb (GObject      *object,
+                                        GAsyncResult *result,
+                                        gpointer      user_data)
+{
+  GFile *file = (GFile *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (G_IS_FILE (file));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  if (!g_file_make_directory_finish (file, result, &error))
+    g_warning ("Failed to make directory: %s", error->message);
+
+  ide_task_return_boolean (task, TRUE);
+}
+
+static void
+gbp_project_tree_pane_actions_mkfile_cb (GObject      *object,
+                                         GAsyncResult *result,
+                                         gpointer      user_data)
+{
+  GFile *file = (GFile *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (G_IS_FILE (file));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  if (!g_file_create_finish (file, result, &error))
+    g_warning ("Failed to make file: %s", error->message);
+
+  ide_task_return_boolean (task, TRUE);
+}
+
+static void
+gbp_project_tree_pane_actions_new_cb (GObject      *object,
+                                      GAsyncResult *result,
+                                      gpointer      user_data)
+{
+  GbpNewFilePopover *popover = (GbpNewFilePopover *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GFile) file = NULL;
+  GCancellable *cancellable;
+  NewState *state;
+
+  g_assert (GBP_IS_NEW_FILE_POPOVER (popover));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  if (!(file = gbp_new_file_popover_display_finish (popover, result, &error)))
+    {
+      ide_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  cancellable = ide_task_get_cancellable (task);
+  state = ide_task_get_task_data (task);
+
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_assert (state != NULL);
+  g_assert (IDE_IS_TREE_NODE (state->node));
+  g_assert (state->file_type);
+  g_assert (state->file == NULL);
+
+  state->file = g_object_ref (file);
+
+  if (state->file_type == G_FILE_TYPE_DIRECTORY)
+    g_file_make_directory_async (file,
+                                 G_PRIORITY_DEFAULT,
+                                 cancellable,
+                                 gbp_project_tree_pane_actions_mkdir_cb,
+                                 g_steal_pointer (&task));
+  else if (state->file_type == G_FILE_TYPE_REGULAR)
+    g_file_create_async (file,
+                         G_FILE_CREATE_NONE,
+                         G_PRIORITY_DEFAULT,
+                         cancellable,
+                         gbp_project_tree_pane_actions_mkfile_cb,
+                         g_steal_pointer (&task));
+  else
+    g_assert_not_reached ();
+
+  gtk_widget_destroy (GTK_WIDGET (popover));
+}
+
+static void
+gbp_project_tree_pane_actions_new (GbpProjectTreePane *self,
+                                   GFileType           file_type)
+{
+  g_autoptr(IdeTask) task = NULL;
+  g_autoptr(GFile) directory = NULL;
+  GbpNewFilePopover *popover;
+  IdeProjectFile *project_file;
+  IdeTreeNode *selected;
+  NewState *state;
+
+  g_assert (GBP_IS_PROJECT_TREE_PANE (self));
+  g_assert (file_type == G_FILE_TYPE_REGULAR ||
+            file_type == G_FILE_TYPE_DIRECTORY);
+
+  /* Nothing to do if there was no selection */
+  if (!(selected = ide_tree_get_selected_node (self->tree)))
+    return;
+
+  /* Select parent if we got an empty node or it's not a directory */
+  if (!ide_tree_node_holds (selected, IDE_TYPE_PROJECT_FILE) ||
+      !(project_file = ide_tree_node_get_item (selected)) ||
+      !ide_project_file_is_directory (project_file))
+    {
+      IdeTreeNode *parent = ide_tree_node_get_parent (selected);
+
+      if (!ide_tree_node_holds (parent, IDE_TYPE_PROJECT_FILE))
+        return;
+
+      project_file = ide_tree_node_get_item (parent);
+      selected = parent;
+
+      ide_tree_select_node (self->tree, parent);
+    }
+
+  /* Now create our async task to keep track of everything during
+   * the asynchronous nature of this workflow (the user entering
+   * infromation, maybe cancelling, and async file creation).
+   */
+  directory = ide_project_file_ref_file (project_file);
+
+  popover = g_object_new (GBP_TYPE_NEW_FILE_POPOVER,
+                          "directory", directory,
+                          "file-type", file_type,
+                          "position", GTK_POS_RIGHT,
+                          NULL);
+
+
+  state = g_slice_new0 (NewState);
+  state->needs_collapse = !ide_tree_node_expanded (self->tree, selected);
+  state->file_type = file_type;
+  state->node = g_object_ref (selected);
+
+  task = ide_task_new (self, NULL, new_action_completed_cb, NULL);
+  ide_task_set_source_tag (task, gbp_project_tree_pane_actions_new);
+  ide_task_set_task_data (task, state, new_state_free);
+
+  gbp_new_file_popover_display_async (popover,
+                                      self->tree,
+                                      selected,
+                                      NULL,
+                                      gbp_project_tree_pane_actions_new_cb,
+                                      g_steal_pointer (&task));
+}
+
+static void
+close_matching_pages (GtkWidget *widget,
+                      gpointer   user_data)
+{
+  IdePage *page = (IdePage *)widget;
+  GFile *file = user_data;
+  GFile *this_file;
+
+  g_assert (IDE_IS_PAGE (page));
+  g_assert (G_IS_FILE (file));
+
+  if (!IDE_IS_EDITOR_PAGE (page))
+    return;
+
+  if (!(this_file = ide_editor_page_get_file (IDE_EDITOR_PAGE (page))))
+    return;
+
+  if (g_file_equal (this_file, file))
+    gtk_widget_destroy (widget);
+}
+
+#define DEFINE_ACTION_HANDLER(short_name, BODY)                       \
+static void                                                           \
+gbp_project_tree_pane_actions_##short_name (GSimpleAction *action,    \
+                                            GVariant      *param,     \
+                                            gpointer       user_data) \
+{                                                                     \
+  GbpProjectTreePane *self = user_data;                               \
+                                                                      \
+  g_assert (G_IS_SIMPLE_ACTION (action));                             \
+  g_assert (GBP_IS_PROJECT_TREE_PANE (self));                         \
+                                                                      \
+  BODY                                                                \
+}
+
+DEFINE_ACTION_HANDLER (new_file, {
+  gbp_project_tree_pane_actions_new (self, G_FILE_TYPE_REGULAR);
+});
+
+DEFINE_ACTION_HANDLER (new_folder, {
+  gbp_project_tree_pane_actions_new (self, G_FILE_TYPE_DIRECTORY);
+});
+
+DEFINE_ACTION_HANDLER (open, {
+  IdeProjectFile *project_file;
+  g_autoptr(GFile) file = NULL;
+  IdeWorkbench *workbench;
+  IdeTreeNode *selected;
+
+  if (!(selected = ide_tree_get_selected_node (self->tree)) ||
+      !ide_tree_node_holds (selected, IDE_TYPE_PROJECT_FILE) ||
+      !(project_file = ide_tree_node_get_item (selected)))
+    return;
+
+  file = ide_project_file_ref_file (project_file);
+  workbench = ide_widget_get_workbench (GTK_WIDGET (self));
+
+  ide_workbench_open_async (workbench,
+                            file,
+                            NULL,
+                            IDE_BUFFER_OPEN_FLAGS_NONE,
+                            NULL, NULL, NULL);
+});
+
+static void
+gbp_project_tree_pane_actions_rename_cb (GObject      *object,
+                                         GAsyncResult *result,
+                                         gpointer      user_data)
+{
+  IdeProject *project = (IdeProject *)object;
+  g_autoptr(GbpProjectTreePane) self = user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_PROJECT_TREE_PANE (self));
+
+  if (!ide_project_rename_file_finish (project, result, &error))
+    g_warning ("Failed to rename file: %s", error->message);
+}
+
+static void
+gbp_project_tree_pane_actions_rename_display_cb (GObject      *object,
+                                                 GAsyncResult *result,
+                                                 gpointer      user_data)
+{
+  GbpRenameFilePopover *popover = (GbpRenameFilePopover *)object;
+  g_autoptr(GbpProjectTreePane) self = user_data;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GFile) dst = NULL;
+  IdeProject *project;
+  IdeContext *context;
+  GFile *src;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_PROJECT_TREE_PANE (self));
+
+  if (!(dst = gbp_rename_file_popover_display_finish (popover, result, &error)))
+    goto destroy;
+
+  src = gbp_rename_file_popover_get_file (popover);
+  context = ide_widget_get_context (GTK_WIDGET (self));
+  project = ide_project_from_context (context);
+
+  ide_project_rename_file_async (project,
+                                 src,
+                                 dst,
+                                 NULL,
+                                 gbp_project_tree_pane_actions_rename_cb,
+                                 g_object_ref (self));
+
+destroy:
+  gtk_widget_destroy (GTK_WIDGET (popover));
+}
+
+DEFINE_ACTION_HANDLER (rename, {
+  IdeProjectFile *project_file;
+  g_autoptr(GFile) file = NULL;
+  GbpRenameFilePopover *popover;
+  IdeWorkbench *workbench;
+  IdeTreeNode *selected;
+  gboolean is_dir;
+
+  if (!(selected = ide_tree_get_selected_node (self->tree)) ||
+      !ide_tree_node_holds (selected, IDE_TYPE_PROJECT_FILE) ||
+      !(project_file = ide_tree_node_get_item (selected)))
+    return;
+
+  is_dir = ide_project_file_is_directory (project_file);
+  file = ide_project_file_ref_file (project_file);
+  workbench = ide_widget_get_workbench (GTK_WIDGET (self->tree));
+  ide_workbench_foreach_page (workbench, close_matching_pages, file);
+
+  popover = g_object_new (GBP_TYPE_RENAME_FILE_POPOVER,
+                          "position", GTK_POS_LEFT,
+                          "is-directory", is_dir,
+                          "file", file,
+                          NULL);
+
+  gbp_rename_file_popover_display_async (popover,
+                                         self->tree,
+                                         selected,
+                                         NULL,
+                                         gbp_project_tree_pane_actions_rename_display_cb,
+                                         g_object_ref (self));
+});
+
+static void
+gbp_project_tree_pane_actions_trash_cb (GObject      *object,
+                                        GAsyncResult *result,
+                                        gpointer      user_data)
+{
+  IdeProjectFile *project_file = (IdeProjectFile *)object;
+  g_autoptr(IdeTreeNode) node = user_data;
+  g_autoptr(GError) error = NULL;
+  IdeTreeNode *parent;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_PROJECT_FILE (project_file));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TREE_NODE (node));
+
+  if (!ide_project_file_trash_finish (project_file, result, &error))
+    return;
+
+  if ((parent = ide_tree_node_get_parent (node)))
+    ide_tree_node_remove (parent, node);
+}
+
+DEFINE_ACTION_HANDLER (trash, {
+  IdeProjectFile *project_file;
+  g_autoptr(GFile) file = NULL;
+  IdeWorkbench *workbench;
+  IdeTreeNode *selected;
+
+  if (!(selected = ide_tree_get_selected_node (self->tree)) ||
+      !ide_tree_node_holds (selected, IDE_TYPE_PROJECT_FILE) ||
+      !(project_file = ide_tree_node_get_item (selected)))
+    return;
+
+  file = ide_project_file_ref_file (project_file);
+  workbench = ide_widget_get_workbench (GTK_WIDGET (self->tree));
+  ide_workbench_foreach_page (workbench, close_matching_pages, file);
+
+  ide_project_file_trash_async (project_file,
+                                NULL,
+                                gbp_project_tree_pane_actions_trash_cb,
+                                g_object_ref (selected));
+});
+
+DEFINE_ACTION_HANDLER (open_containing_folder, {
+  IdeProjectFile *project_file;
+  g_autoptr(GFile) file = NULL;
+  IdeTreeNode *selected;
+
+  if (!(selected = ide_tree_get_selected_node (self->tree)) ||
+      !ide_tree_node_holds (selected, IDE_TYPE_PROJECT_FILE) ||
+      !(project_file = ide_tree_node_get_item (selected)))
+    return;
+
+  file = ide_project_file_ref_file (project_file);
+  dzl_file_manager_show (file, NULL);
+});
+
+DEFINE_ACTION_HANDLER (open_with_hint, {
+  IdeProjectFile *project_file;
+  g_autoptr(GFile) file = NULL;
+  IdeWorkbench *workbench;
+  IdeTreeNode *selected;
+  const gchar *hint;
+
+  if (!(selected = ide_tree_get_selected_node (self->tree)) ||
+      !ide_tree_node_holds (selected, IDE_TYPE_PROJECT_FILE) ||
+      !(project_file = ide_tree_node_get_item (selected)) ||
+      !(hint = g_variant_get_string (param, NULL)))
+    return;
+
+  workbench = ide_widget_get_workbench (GTK_WIDGET (self));
+  file = ide_project_file_ref_file (project_file);
+
+  ide_workbench_open_async (workbench,
+                            file,
+                            hint,
+                            IDE_BUFFER_OPEN_FLAGS_NONE,
+                            NULL, NULL, NULL);
+});
+
+/* Based on gdesktopappinfo.c in GIO */
+static gchar *
+find_terminal_executable (void)
+{
+  gsize i;
+  gchar *path = NULL;
+  g_autoptr(GSettings) terminal_settings = NULL;
+  g_autofree gchar *gsettings_terminal = NULL;
+  const gchar *terminals[] = {
+    NULL,                     /* GSettings */
+    "x-terminal-emulator",    /* Debian's alternative system */
+    "gnome-terminal",
+    NULL,                     /* getenv ("TERM") */
+    "nxterm", "color-xterm",
+    "rxvt", "xterm", "dtterm"
+  };
+
+  /* This is deprecated, but at least the user can specify it! */
+  terminal_settings = g_settings_new ("org.gnome.desktop.default-applications.terminal");
+  gsettings_terminal = g_settings_get_string (terminal_settings, "exec");
+  terminals[0] = gsettings_terminal;
+
+  /* This is generally one of the fallback terminals */
+  terminals[3] = g_getenv ("TERM");
+
+  for (i = 0; i < G_N_ELEMENTS (terminals) && path == NULL; ++i)
+    {
+      if (terminals[i] != NULL)
+        {
+          G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+          path = g_find_program_in_path (terminals[i]);
+          G_GNUC_END_IGNORE_DEPRECATIONS
+        }
+    }
+
+  return path;
+}
+
+static void
+gbp_project_tree_pane_actions_open_in_terminal (GSimpleAction *action,
+                                                GVariant      *param,
+                                                gpointer       user_data)
+{
+  GbpProjectTreePane *self = user_data;
+  IdeProjectFile *project_file;
+  g_autoptr(GFile) file = NULL;
+  IdeTreeNode *selected;
+  g_autofree gchar *terminal_executable = NULL;
+  const gchar *argv[] = { NULL, NULL };
+  g_auto(GStrv) env = NULL;
+  g_autoptr(GFile) workdir = NULL;
+  g_autoptr(GError) error = NULL;
+
+  if (!(selected = ide_tree_get_selected_node (self->tree)) ||
+      !ide_tree_node_holds (selected, IDE_TYPE_PROJECT_FILE) ||
+      !(project_file = ide_tree_node_get_item (selected)))
+    return;
+
+  if (ide_project_file_is_directory (project_file))
+    workdir = ide_project_file_ref_file (project_file);
+  else
+    workdir = g_object_ref (ide_project_file_get_directory (project_file));
+
+  if (!g_file_is_native (workdir))
+    {
+      g_warning ("Not a native directory, cannot open terminal");
+      return;
+    }
+
+  terminal_executable = find_terminal_executable ();
+  argv[0] = terminal_executable;
+  g_return_if_fail (terminal_executable != NULL);
+
+  env = g_get_environ ();
+
+  {
+    /*
+     * Overwrite SHELL to the users default shell.
+     * Failure to do so typically results in /bin/sh being used.
+     */
+    g_autofree gchar *shell = vte_get_user_shell ();
+    env = g_environ_setenv (env, "SHELL", shell, TRUE);
+  }
+
+  /* Can't use GdkAppLaunchContext as
+   * we cannot set the working directory.
+   */
+  if (!g_spawn_async (g_file_peek_path (workdir),
+                      (gchar **)argv, env,
+                      G_SPAWN_STDERR_TO_DEV_NULL,
+                      NULL, NULL, NULL, &error))
+    /* translators: %s is replaced with the error message */
+    g_warning ("Failed to spawn terminal: %s", error->message);
+}
+
+static const GActionEntry entries[] = {
+  { "new-file", gbp_project_tree_pane_actions_new_file },
+  { "new-folder", gbp_project_tree_pane_actions_new_folder },
+  { "open", gbp_project_tree_pane_actions_open },
+  { "open-with-hint", gbp_project_tree_pane_actions_open_with_hint, "s" },
+  { "open-containing-folder", gbp_project_tree_pane_actions_open_containing_folder },
+  { "open-in-terminal", gbp_project_tree_pane_actions_open_in_terminal },
+  { "rename", gbp_project_tree_pane_actions_rename },
+  { "trash", gbp_project_tree_pane_actions_trash },
+};
+
+void
+_gbp_project_tree_pane_init_actions (GbpProjectTreePane *self)
+{
+  g_autoptr(GSimpleActionGroup) actions = NULL;
+
+  g_assert (GBP_IS_PROJECT_TREE_PANE (self));
+
+  actions = g_simple_action_group_new ();
+  g_action_map_add_action_entries (G_ACTION_MAP (actions),
+                                   entries,
+                                   G_N_ELEMENTS (entries),
+                                   self);
+  gtk_widget_insert_action_group (GTK_WIDGET (self->tree),
+                                  "project-tree",
+                                  G_ACTION_GROUP (actions));
+
+  _gbp_project_tree_pane_update_actions (self);
+}
+
+void
+_gbp_project_tree_pane_update_actions (GbpProjectTreePane *self)
+{
+  GtkTreeSelection *selection;
+  gboolean is_file = FALSE;
+  gboolean is_dir = FALSE;
+
+  g_assert (GBP_IS_PROJECT_TREE_PANE (self));
+
+  if ((selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->tree))))
+    {
+      GtkTreeIter iter;
+
+      if (gtk_tree_selection_get_selected (selection, NULL, &iter))
+        {
+          IdeTreeModel *model = IDE_TREE_MODEL (gtk_tree_view_get_model (GTK_TREE_VIEW (self->tree)));
+          IdeTreeNode *node = ide_tree_model_get_node (model, &iter);
+          GObject *item = ide_tree_node_get_item (node);
+
+          if ((is_file = IDE_IS_PROJECT_FILE (item)))
+            is_dir = ide_project_file_is_directory (IDE_PROJECT_FILE (item));
+        }
+    }
+
+  dzl_gtk_widget_action_set (GTK_WIDGET (self->tree), "project-tree", "new-file",
+                             "enabled", is_file,
+                             NULL);
+  dzl_gtk_widget_action_set (GTK_WIDGET (self->tree), "project-tree", "new-folder",
+                             "enabled", is_file,
+                             NULL);
+  dzl_gtk_widget_action_set (GTK_WIDGET (self->tree), "project-tree", "trash",
+                             "enabled", is_file,
+                             NULL);
+  dzl_gtk_widget_action_set (GTK_WIDGET (self->tree), "project-tree", "rename",
+                             "enabled", is_file,
+                             NULL);
+  dzl_gtk_widget_action_set (GTK_WIDGET (self->tree), "project-tree", "open",
+                             "enabled", is_file && !is_dir,
+                             NULL);
+  dzl_gtk_widget_action_set (GTK_WIDGET (self->tree), "project-tree", "open-with-hint",
+                             "enabled", is_file && !is_dir,
+                             NULL);
+  dzl_gtk_widget_action_set (GTK_WIDGET (self->tree), "project-tree", "open-containing-folder",
+                             "enabled", is_file,
+                             NULL);
+  dzl_gtk_widget_action_set (GTK_WIDGET (self->tree), "project-tree", "open-in-terminal",
+                             "enabled", is_file,
+                             NULL);
+}
diff --git a/src/plugins/project-tree/gbp-project-tree-pane.c 
b/src/plugins/project-tree/gbp-project-tree-pane.c
new file mode 100644
index 000000000..9070e4b92
--- /dev/null
+++ b/src/plugins/project-tree/gbp-project-tree-pane.c
@@ -0,0 +1,62 @@
+/* gbp-project-tree-pane.c
+ *
+ * Copyright 2018-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 "gbp-project-tree-pane"
+
+#include "config.h"
+
+#include "gbp-project-tree-private.h"
+#include "gbp-project-tree.h"
+
+G_DEFINE_TYPE (GbpProjectTreePane, gbp_project_tree_pane, IDE_TYPE_PANE)
+
+static void
+gbp_project_tree_pane_class_init (GbpProjectTreePaneClass *klass)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/plugins/project-tree/gbp-project-tree-pane.ui");
+  gtk_widget_class_bind_template_child (widget_class, GbpProjectTreePane, tree);
+
+  g_type_ensure (GBP_TYPE_PROJECT_TREE);
+}
+
+static void
+gbp_project_tree_pane_init (GbpProjectTreePane *self)
+{
+  GtkTreeSelection *selection;
+  IdeApplication *app;
+  GMenu *menu;
+
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  app = IDE_APPLICATION_DEFAULT;
+  menu = dzl_application_get_menu_by_id (DZL_APPLICATION (app), "project-tree-menu");
+  ide_tree_set_context_menu (self->tree, menu);
+
+  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->tree));
+  g_signal_connect_object (selection,
+                           "changed",
+                           G_CALLBACK (_gbp_project_tree_pane_update_actions),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  _gbp_project_tree_pane_init_actions (self);
+}
diff --git a/src/plugins/project-tree/gb-project-tree-editor-addin.h 
b/src/plugins/project-tree/gbp-project-tree-pane.h
similarity index 70%
rename from src/plugins/project-tree/gb-project-tree-editor-addin.h
rename to src/plugins/project-tree/gbp-project-tree-pane.h
index 013bc6ce2..9a47d78eb 100644
--- a/src/plugins/project-tree/gb-project-tree-editor-addin.h
+++ b/src/plugins/project-tree/gbp-project-tree-pane.h
@@ -1,6 +1,6 @@
-/* gb-project-tree-editor-addin.h
+/* gbp-project-tree-pane.h
  *
- * Copyright 2015-2019 Christian Hergert <chergert redhat com>
+ * Copyright 2018-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
@@ -20,12 +20,12 @@
 
 #pragma once
 
-#include <ide.h>
+#include <libide-gui.h>
 
 G_BEGIN_DECLS
 
-#define GB_TYPE_PROJECT_TREE_EDITOR_ADDIN (gb_project_tree_editor_addin_get_type())
+#define GBP_TYPE_PROJECT_TREE_PANE (gbp_project_tree_pane_get_type())
 
-G_DECLARE_FINAL_TYPE (GbProjectTreeEditorAddin, gb_project_tree_editor_addin, GB, PROJECT_TREE_EDITOR_ADDIN, 
GObject)
+G_DECLARE_FINAL_TYPE (GbpProjectTreePane, gbp_project_tree_pane, GBP, PROJECT_TREE_PANE, IdePane)
 
 G_END_DECLS
diff --git a/src/plugins/project-tree/gbp-project-tree-pane.ui 
b/src/plugins/project-tree/gbp-project-tree-pane.ui
new file mode 100644
index 000000000..c99ca7bbd
--- /dev/null
+++ b/src/plugins/project-tree/gbp-project-tree-pane.ui
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GbpProjectTreePane" parent="IdePane">
+    <child>
+      <object class="GtkScrolledWindow">
+        <property name="visible">true</property>
+        <child>
+          <object class="GbpProjectTree" id="tree">
+            <property name="level-indentation">16</property>
+            <property name="visible">true</property>
+            <style>
+              <class name="i-wanna-be-list-box"/>
+              <class name="project-tree"/>
+            </style>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/plugins/project-tree/gb-project-tree-private.h 
b/src/plugins/project-tree/gbp-project-tree-private.h
similarity index 64%
rename from src/plugins/project-tree/gb-project-tree-private.h
rename to src/plugins/project-tree/gbp-project-tree-private.h
index cfa740317..ba0aa052d 100644
--- a/src/plugins/project-tree/gb-project-tree-private.h
+++ b/src/plugins/project-tree/gbp-project-tree-private.h
@@ -1,6 +1,6 @@
-/* gb-project-tree-private.h
+/* gbp-project-tree-private.h
  *
- * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ * Copyright 2018-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
@@ -20,21 +20,21 @@
 
 #pragma once
 
-#include <ide.h>
+#include <libide-gui.h>
+#include <libide-tree.h>
+
+#include "gbp-project-tree-pane.h"
 
 G_BEGIN_DECLS
 
-struct _GbProjectTree
+struct _GbpProjectTreePane
 {
-  DzlTree           parent_instance;
-
-  GSettings        *settings;
-  PeasExtensionSet *addins;
-
-  guint             expanded_in_new : 1;
-  guint             show_ignored_files : 1;
+  IdePane       parent_instance;
+  IdeTree      *tree;
+  guint         has_loaded : 1;
 };
 
-void      _gb_project_tree_init_shortcuts       (GbProjectTree *self);
+void _gbp_project_tree_pane_init_actions   (GbpProjectTreePane *self);
+void _gbp_project_tree_pane_update_actions (GbpProjectTreePane *self);
 
 G_END_DECLS
diff --git a/src/plugins/project-tree/gbp-project-tree-workspace-addin.c 
b/src/plugins/project-tree/gbp-project-tree-workspace-addin.c
new file mode 100644
index 000000000..394bf326a
--- /dev/null
+++ b/src/plugins/project-tree/gbp-project-tree-workspace-addin.c
@@ -0,0 +1,102 @@
+/* gbp-project-tree-workspace-addin.c
+ *
+ * Copyright 2018-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 "gbp-project-tree-workspace-addin"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <libide-editor.h>
+#include <libide-gui.h>
+
+#include "gbp-project-tree-workspace-addin.h"
+#include "gbp-project-tree-pane.h"
+
+struct _GbpProjectTreeWorkspaceAddin
+{
+  GObject             parent_instance;
+  GbpProjectTreePane *pane;
+};
+
+static void
+gbp_project_tree_workspace_addin_load (IdeWorkspaceAddin *addin,
+                                       IdeWorkspace      *workspace)
+{
+  GbpProjectTreeWorkspaceAddin *self = (GbpProjectTreeWorkspaceAddin *)addin;
+  IdeEditorSidebar *sidebar;
+  IdeSurface *surface;
+
+  g_assert (GBP_IS_PROJECT_TREE_WORKSPACE_ADDIN (self));
+  g_assert (IDE_IS_PRIMARY_WORKSPACE (workspace));
+
+  surface = ide_workspace_get_surface_by_name (workspace, "editor");
+  g_assert (IDE_IS_EDITOR_SURFACE (surface));
+
+  sidebar = ide_editor_surface_get_sidebar (IDE_EDITOR_SURFACE (surface));
+  g_assert (IDE_IS_EDITOR_SIDEBAR (sidebar));
+
+  self->pane = g_object_new (GBP_TYPE_PROJECT_TREE_PANE,
+                             "visible", TRUE,
+                             NULL);
+  g_signal_connect (self->pane,
+                    "destroy",
+                    G_CALLBACK (gtk_widget_destroyed),
+                    &self->pane);
+  ide_editor_sidebar_add_section (sidebar,
+                                  "project-tree",
+                                  _("Project Tree"),
+                                  "view-list-symbolic",
+                                  NULL, NULL,
+                                  GTK_WIDGET (self->pane),
+                                  0);
+}
+
+static void
+gbp_project_tree_workspace_addin_unload (IdeWorkspaceAddin *addin,
+                                         IdeWorkspace      *workspace)
+{
+  GbpProjectTreeWorkspaceAddin *self = (GbpProjectTreeWorkspaceAddin *)addin;
+
+  g_assert (GBP_IS_PROJECT_TREE_WORKSPACE_ADDIN (self));
+  g_assert (IDE_IS_PRIMARY_WORKSPACE (workspace));
+
+  if (self->pane != NULL)
+    gtk_widget_destroy (GTK_WIDGET (self->pane));
+}
+
+static void
+workspace_addin_iface_init (IdeWorkspaceAddinInterface *iface)
+{
+  iface->load = gbp_project_tree_workspace_addin_load;
+  iface->unload = gbp_project_tree_workspace_addin_unload;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GbpProjectTreeWorkspaceAddin, gbp_project_tree_workspace_addin, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (IDE_TYPE_WORKSPACE_ADDIN, workspace_addin_iface_init))
+
+static void
+gbp_project_tree_workspace_addin_class_init (GbpProjectTreeWorkspaceAddinClass *klass)
+{
+}
+
+static void
+gbp_project_tree_workspace_addin_init (GbpProjectTreeWorkspaceAddin *self)
+{
+}
diff --git a/src/plugins/project-tree/gb-vcs-tree-builder.h 
b/src/plugins/project-tree/gbp-project-tree-workspace-addin.h
similarity index 68%
rename from src/plugins/project-tree/gb-vcs-tree-builder.h
rename to src/plugins/project-tree/gbp-project-tree-workspace-addin.h
index 16fc64747..796a0163c 100644
--- a/src/plugins/project-tree/gb-vcs-tree-builder.h
+++ b/src/plugins/project-tree/gbp-project-tree-workspace-addin.h
@@ -1,6 +1,6 @@
-/* gb-vcs-tree-builder.h
+/* gbp-project-tree-workspace-addin.h
  *
- * Copyright 2017-2019 Christian Hergert <chergert redhat com>
+ * Copyright 2018-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
@@ -20,14 +20,12 @@
 
 #pragma once
 
-#include <dazzle.h>
+#include <glib-object.h>
 
 G_BEGIN_DECLS
 
-#define GB_TYPE_VCS_TREE_BUILDER (gb_vcs_tree_builder_get_type())
+#define GBP_TYPE_PROJECT_TREE_WORKSPACE_ADDIN (gbp_project_tree_workspace_addin_get_type())
 
-G_DECLARE_FINAL_TYPE (GbVcsTreeBuilder, gb_vcs_tree_builder, GB, VCS_TREE_BUILDER, DzlTreeBuilder)
-
-DzlTreeBuilder *gb_vcs_tree_builder_new (void);
+G_DECLARE_FINAL_TYPE (GbpProjectTreeWorkspaceAddin, gbp_project_tree_workspace_addin, GBP, 
PROJECT_TREE_WORKSPACE_ADDIN, GObject)
 
 G_END_DECLS
diff --git a/src/plugins/project-tree/gbp-project-tree.c b/src/plugins/project-tree/gbp-project-tree.c
new file mode 100644
index 000000000..5fd644118
--- /dev/null
+++ b/src/plugins/project-tree/gbp-project-tree.c
@@ -0,0 +1,178 @@
+/* gbp-project-tree.c
+ *
+ * Copyright 2018-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 "gbp-project-tree"
+
+#include "config.h"
+
+#include <libide-gui.h>
+#include <libide-projects.h>
+
+#include "gbp-project-tree.h"
+
+struct _GbpProjectTree
+{
+  IdeTree parent_instance;
+};
+
+G_DEFINE_TYPE (GbpProjectTree, gbp_project_tree, IDE_TYPE_TREE)
+
+static IdeTreeNodeVisit
+locate_project_files (IdeTreeNode *node,
+                      gpointer     user_data)
+{
+  IdeTreeNode **out_node = user_data;
+
+  if (ide_tree_node_holds (node, IDE_TYPE_PROJECT_FILE))
+    {
+      *out_node = node;
+      return IDE_TREE_NODE_VISIT_BREAK;
+    }
+
+  return IDE_TREE_NODE_VISIT_CONTINUE;
+}
+
+static void
+project_files_expanded_cb (GObject      *object,
+                           GAsyncResult *result,
+                           gpointer      user_data)
+{
+  IdeTreeModel *model = (IdeTreeModel *)object;
+  g_autoptr(IdeTask) task = user_data;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_TREE_MODEL (model));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  if (ide_tree_model_expand_finish (model, result, NULL))
+    {
+      g_autoptr(GtkTreePath) path = NULL;
+      GbpProjectTree *self;
+      IdeTreeNode *node;
+
+      self = ide_task_get_source_object (task);
+      node = ide_task_get_task_data (task);
+
+      g_assert (GBP_IS_PROJECT_TREE (self));
+      g_assert (IDE_IS_TREE_NODE (node));
+
+      if ((path = ide_tree_node_get_path (node)))
+        gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
+    }
+
+  ide_task_return_boolean (task, TRUE);
+}
+
+static void
+gbp_project_tree_expand_cb (GObject      *object,
+                            GAsyncResult *result,
+                            gpointer      user_data)
+{
+  IdeTreeModel *model = (IdeTreeModel *)object;
+  g_autoptr(IdeTask) task = user_data;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_TREE_MODEL (model));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  if (ide_tree_model_expand_finish (model, result, NULL))
+    {
+      IdeTreeNode *root = ide_tree_model_get_root (model);
+      IdeTreeNode *node = NULL;
+
+      ide_tree_node_traverse (root,
+                              G_PRE_ORDER,
+                              G_TRAVERSE_ALL,
+                              1,
+                              locate_project_files,
+                              &node);
+
+      if (node == NULL)
+        goto cleanup;
+
+      ide_task_set_task_data (task, g_object_ref (node), g_object_unref);
+
+      ide_tree_model_expand_async (model,
+                                   node,
+                                   NULL,
+                                   project_files_expanded_cb,
+                                   g_steal_pointer (&task));
+
+      return;
+    }
+
+cleanup:
+  ide_task_return_boolean (task, TRUE);
+}
+
+static void
+gbp_project_tree_hierarchy_changed (GtkWidget *widget,
+                                    GtkWidget *old_toplevel)
+{
+  GbpProjectTree *self = (GbpProjectTree *)widget;
+  GtkWidget *toplevel;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_PROJECT_TREE (self));
+
+  toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
+
+  if (IDE_IS_WORKSPACE (toplevel))
+    {
+      IdeContext *context = ide_widget_get_context (GTK_WIDGET (toplevel));
+      g_autoptr(IdeTreeNode) root = ide_tree_node_new ();
+      g_autoptr(IdeTreeModel) model = NULL;
+      g_autoptr(IdeTask) task = NULL;
+
+      model = g_object_new (IDE_TYPE_TREE_MODEL,
+                            "kind", "project-tree",
+                            "tree", self,
+                            NULL);
+      gtk_tree_view_set_model (GTK_TREE_VIEW (self), GTK_TREE_MODEL (model));
+
+      ide_tree_node_set_item (root, context);
+      ide_object_append (IDE_OBJECT (context), IDE_OBJECT (model));
+      ide_tree_model_set_root (model, root);
+
+      task = ide_task_new (self, NULL, NULL, NULL);
+      ide_task_set_source_tag (task, gbp_project_tree_hierarchy_changed);
+
+      ide_tree_model_expand_async (model,
+                                   root,
+                                   NULL,
+                                   gbp_project_tree_expand_cb,
+                                   g_steal_pointer (&task));
+    }
+}
+
+static void
+gbp_project_tree_class_init (GbpProjectTreeClass *klass)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  widget_class->hierarchy_changed = gbp_project_tree_hierarchy_changed;
+}
+
+static void
+gbp_project_tree_init (GbpProjectTree *self)
+{
+}
diff --git a/src/plugins/project-tree/gb-project-tree-actions.h b/src/plugins/project-tree/gbp-project-tree.h
similarity index 74%
rename from src/plugins/project-tree/gb-project-tree-actions.h
rename to src/plugins/project-tree/gbp-project-tree.h
index c63617954..1a1404dbe 100644
--- a/src/plugins/project-tree/gb-project-tree-actions.h
+++ b/src/plugins/project-tree/gbp-project-tree.h
@@ -1,6 +1,6 @@
-/* gb-project-tree-actions.h
+/* gbp-project-tree.h
  *
- * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ * Copyright 2018-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
@@ -20,11 +20,12 @@
 
 #pragma once
 
-#include "gb-project-tree.h"
+#include <libide-tree.h>
 
 G_BEGIN_DECLS
 
-void gb_project_tree_actions_init   (GbProjectTree *self);
-void gb_project_tree_actions_update (GbProjectTree *self);
+#define GBP_TYPE_PROJECT_TREE (gbp_project_tree_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpProjectTree, gbp_project_tree, GBP, PROJECT_TREE, IdeTree)
 
 G_END_DECLS
diff --git a/src/plugins/project-tree/gb-rename-file-popover.c 
b/src/plugins/project-tree/gbp-rename-file-popover.c
similarity index 58%
rename from src/plugins/project-tree/gb-rename-file-popover.c
rename to src/plugins/project-tree/gbp-rename-file-popover.c
index b26eec218..2453d000f 100644
--- a/src/plugins/project-tree/gb-rename-file-popover.c
+++ b/src/plugins/project-tree/gbp-rename-file-popover.c
@@ -1,4 +1,4 @@
-/* gb-rename-file-popover.c
+/* gbp-rename-file-popover.c
  *
  * Copyright 2015-2019 Christian Hergert <christian hergert me>
  *
@@ -18,17 +18,20 @@
  * SPDX-License-Identifier: GPL-3.0-or-later
  */
 
+#define G_LOG_DOMAIN "gbp-rename-file-popover"
+
 #include <glib/gi18n.h>
-#include <ide.h>
+#include <libide-gui.h>
 
-#include "gb-rename-file-popover.h"
+#include "gbp-rename-file-popover.h"
 
-struct _GbRenameFilePopover
+struct _GbpRenameFilePopover
 {
   GtkPopover    parent_instance;
 
   GCancellable *cancellable;
   GFile        *file;
+  IdeTask      *task;
 
   GtkEntry     *entry;
   GtkButton    *button;
@@ -42,32 +45,32 @@ enum {
   PROP_0,
   PROP_FILE,
   PROP_IS_DIRECTORY,
-  LAST_PROP
+  N_PROPS
 };
 
 enum {
   RENAME_FILE,
-  LAST_SIGNAL
+  N_SIGNALS
 };
 
-G_DEFINE_TYPE (GbRenameFilePopover, gb_rename_file_popover, GTK_TYPE_POPOVER)
+G_DEFINE_TYPE (GbpRenameFilePopover, gbp_rename_file_popover, GTK_TYPE_POPOVER)
 
-static GParamSpec *properties [LAST_PROP];
-static guint signals [LAST_SIGNAL];
+static GParamSpec *properties [N_PROPS];
+static guint signals [N_SIGNALS];
 
 GFile *
-gb_rename_file_popover_get_file (GbRenameFilePopover *self)
+gbp_rename_file_popover_get_file (GbpRenameFilePopover *self)
 {
-  g_return_val_if_fail (GB_IS_RENAME_FILE_POPOVER (self), NULL);
+  g_return_val_if_fail (GBP_IS_RENAME_FILE_POPOVER (self), NULL);
 
   return self->file;
 }
 
 static void
-gb_rename_file_popover_set_file (GbRenameFilePopover *self,
+gbp_rename_file_popover_set_file (GbpRenameFilePopover *self,
                                  GFile               *file)
 {
-  g_return_if_fail (GB_IS_RENAME_FILE_POPOVER (self));
+  g_return_if_fail (GBP_IS_RENAME_FILE_POPOVER (self));
   g_return_if_fail (G_IS_FILE (file));
 
   if (g_set_object (&self->file, file))
@@ -89,10 +92,10 @@ gb_rename_file_popover_set_file (GbRenameFilePopover *self,
 }
 
 static void
-gb_rename_file_popover_set_is_directory (GbRenameFilePopover *self,
+gbp_rename_file_popover_set_is_directory (GbpRenameFilePopover *self,
                                          gboolean             is_directory)
 {
-  g_return_if_fail (GB_IS_RENAME_FILE_POPOVER (self));
+  g_return_if_fail (GBP_IS_RENAME_FILE_POPOVER (self));
 
   is_directory = !!is_directory;
 
@@ -104,13 +107,13 @@ gb_rename_file_popover_set_is_directory (GbRenameFilePopover *self,
 }
 
 static void
-gb_rename_file_popover__file_query_info (GObject      *object,
+gbp_rename_file_popover__file_query_info (GObject      *object,
                                          GAsyncResult *result,
                                          gpointer      user_data)
 {
   GFile *file = (GFile *)object;
   g_autoptr(GFileInfo) file_info = NULL;
-  g_autoptr(GbRenameFilePopover) self = user_data;
+  g_autoptr(GbpRenameFilePopover) self = user_data;
   g_autoptr(GError) error = NULL;
   GFileType file_type;
 
@@ -147,14 +150,14 @@ gb_rename_file_popover__file_query_info (GObject      *object,
 }
 
 static void
-gb_rename_file_popover__entry_changed (GbRenameFilePopover *self,
+gbp_rename_file_popover__entry_changed (GbpRenameFilePopover *self,
                                        GtkEntry            *entry)
 {
   g_autoptr(GFile) parent = NULL;
   g_autoptr(GFile) file = NULL;
   const gchar *text;
 
-  g_assert (GB_IS_RENAME_FILE_POPOVER (self));
+  g_assert (GBP_IS_RENAME_FILE_POPOVER (self));
   g_assert (GTK_IS_ENTRY (entry));
   g_assert (self->file != NULL);
   g_assert (G_IS_FILE (self->file));
@@ -163,7 +166,7 @@ gb_rename_file_popover__entry_changed (GbRenameFilePopover *self,
   gtk_label_set_label (self->message, NULL);
 
   text = gtk_entry_get_text (entry);
-  if (dzl_str_empty0 (text))
+  if (ide_str_empty0 (text))
     return;
 
   if (self->cancellable)
@@ -182,15 +185,15 @@ gb_rename_file_popover__entry_changed (GbRenameFilePopover *self,
                            G_FILE_QUERY_INFO_NONE,
                            G_PRIORITY_DEFAULT,
                            self->cancellable,
-                           gb_rename_file_popover__file_query_info,
+                           gbp_rename_file_popover__file_query_info,
                            g_object_ref (self));
 }
 
 static void
-gb_rename_file_popover__entry_activate (GbRenameFilePopover *self,
+gbp_rename_file_popover__entry_activate (GbpRenameFilePopover *self,
                                         GtkEntry            *entry)
 {
-  g_assert (GB_IS_RENAME_FILE_POPOVER (self));
+  g_assert (GBP_IS_RENAME_FILE_POPOVER (self));
   g_assert (GTK_IS_ENTRY (entry));
 
   if (gtk_widget_get_sensitive (GTK_WIDGET (self->button)))
@@ -198,14 +201,14 @@ gb_rename_file_popover__entry_activate (GbRenameFilePopover *self,
 }
 
 static void
-gb_rename_file_popover__entry_focus_in_event (GbRenameFilePopover *self,
+gbp_rename_file_popover__entry_focus_in_event (GbpRenameFilePopover *self,
                                               GdkEvent            *event,
                                               GtkEntry            *entry)
 {
   const gchar *name;
   const gchar *tmp;
 
-  g_assert (GB_IS_RENAME_FILE_POPOVER (self));
+  g_assert (GBP_IS_RENAME_FILE_POPOVER (self));
   g_assert (GTK_IS_ENTRY (entry));
 
   name = gtk_entry_get_text (entry);
@@ -215,20 +218,21 @@ gb_rename_file_popover__entry_focus_in_event (GbRenameFilePopover *self,
 }
 
 static void
-gb_rename_file_popover__button_clicked (GbRenameFilePopover *self,
+gbp_rename_file_popover__button_clicked (GbpRenameFilePopover *self,
                                         GtkButton           *button)
 {
   g_autoptr(GFile) file = NULL;
   g_autoptr(GFile) parent = NULL;
+  g_autoptr(IdeTask) task = NULL;
   const gchar *path;
 
-  g_assert (GB_IS_RENAME_FILE_POPOVER (self));
+  g_assert (GBP_IS_RENAME_FILE_POPOVER (self));
   g_assert (GTK_IS_BUTTON (button));
   g_assert (self->file != NULL);
   g_assert (G_IS_FILE (self->file));
 
   path = gtk_entry_get_text (self->entry);
-  if (dzl_str_empty0 (path))
+  if (ide_str_empty0 (path))
     return;
 
   parent = g_file_get_parent (self->file);
@@ -238,12 +242,32 @@ gb_rename_file_popover__button_clicked (GbRenameFilePopover *self,
   gtk_widget_set_sensitive (GTK_WIDGET (self->button), FALSE);
 
   g_signal_emit (self, signals [RENAME_FILE], 0, self->file, file);
+
+  /* Complete our async op */
+  if ((task = g_steal_pointer (&self->task)))
+    ide_task_return_pointer (task, g_steal_pointer (&file), g_object_unref);
 }
 
 static void
-gb_rename_file_popover_finalize (GObject *object)
+gbp_rename_file_popover_closed (GtkPopover *popover)
 {
-  GbRenameFilePopover *self = (GbRenameFilePopover *)object;
+  g_autoptr(IdeTask) task = NULL;
+  GbpRenameFilePopover *self = (GbpRenameFilePopover *)popover;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_RENAME_FILE_POPOVER (self));
+
+  if ((task = g_steal_pointer (&self->task)))
+    ide_task_return_new_error (task,
+                               G_IO_ERROR,
+                               G_IO_ERROR_CANCELLED,
+                               "The popover was cancelled");
+}
+
+static void
+gbp_rename_file_popover_finalize (GObject *object)
+{
+  GbpRenameFilePopover *self = (GbpRenameFilePopover *)object;
 
   if (self->cancellable != NULL)
     {
@@ -254,16 +278,18 @@ gb_rename_file_popover_finalize (GObject *object)
 
   g_clear_object (&self->file);
 
-  G_OBJECT_CLASS (gb_rename_file_popover_parent_class)->finalize (object);
+  g_assert (self->task == NULL);
+
+  G_OBJECT_CLASS (gbp_rename_file_popover_parent_class)->finalize (object);
 }
 
 static void
-gb_rename_file_popover_get_property (GObject    *object,
+gbp_rename_file_popover_get_property (GObject    *object,
                                      guint       prop_id,
                                      GValue     *value,
                                      GParamSpec *pspec)
 {
-  GbRenameFilePopover *self = GB_RENAME_FILE_POPOVER (object);
+  GbpRenameFilePopover *self = GBP_RENAME_FILE_POPOVER (object);
 
   switch (prop_id)
     {
@@ -281,21 +307,21 @@ gb_rename_file_popover_get_property (GObject    *object,
 }
 
 static void
-gb_rename_file_popover_set_property (GObject      *object,
+gbp_rename_file_popover_set_property (GObject      *object,
                                      guint         prop_id,
                                      const GValue *value,
                                      GParamSpec   *pspec)
 {
-  GbRenameFilePopover *self = GB_RENAME_FILE_POPOVER (object);
+  GbpRenameFilePopover *self = GBP_RENAME_FILE_POPOVER (object);
 
   switch (prop_id)
     {
     case PROP_FILE:
-      gb_rename_file_popover_set_file (self, g_value_get_object (value));
+      gbp_rename_file_popover_set_file (self, g_value_get_object (value));
       break;
 
     case PROP_IS_DIRECTORY:
-      gb_rename_file_popover_set_is_directory (self, g_value_get_boolean (value));
+      gbp_rename_file_popover_set_is_directory (self, g_value_get_boolean (value));
       break;
 
     default:
@@ -304,14 +330,17 @@ gb_rename_file_popover_set_property (GObject      *object,
 }
 
 static void
-gb_rename_file_popover_class_init (GbRenameFilePopoverClass *klass)
+gbp_rename_file_popover_class_init (GbpRenameFilePopoverClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GtkPopoverClass *popover_class = GTK_POPOVER_CLASS (klass);
+
+  object_class->finalize = gbp_rename_file_popover_finalize;
+  object_class->get_property = gbp_rename_file_popover_get_property;
+  object_class->set_property = gbp_rename_file_popover_set_property;
 
-  object_class->finalize = gb_rename_file_popover_finalize;
-  object_class->get_property = gb_rename_file_popover_get_property;
-  object_class->set_property = gb_rename_file_popover_set_property;
+  popover_class->closed = gbp_rename_file_popover_closed;
 
   properties [PROP_FILE] =
     g_param_spec_object ("file",
@@ -327,7 +356,7 @@ gb_rename_file_popover_class_init (GbRenameFilePopoverClass *klass)
                           FALSE,
                           (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
 
-  g_object_class_install_properties (object_class, LAST_PROP, properties);
+  g_object_class_install_properties (object_class, N_PROPS, properties);
 
   signals [RENAME_FILE] =
     g_signal_new ("rename-file",
@@ -340,39 +369,84 @@ gb_rename_file_popover_class_init (GbRenameFilePopoverClass *klass)
                   G_TYPE_FILE,
                   G_TYPE_FILE);
 
-  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/plugins/project-tree-plugin/gb-rename-file-popover.ui");
-  gtk_widget_class_bind_template_child (widget_class, GbRenameFilePopover, button);
-  gtk_widget_class_bind_template_child (widget_class, GbRenameFilePopover, entry);
-  gtk_widget_class_bind_template_child (widget_class, GbRenameFilePopover, label);
-  gtk_widget_class_bind_template_child (widget_class, GbRenameFilePopover, message);
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/plugins/project-tree/gbp-rename-file-popover.ui");
+  gtk_widget_class_bind_template_child (widget_class, GbpRenameFilePopover, button);
+  gtk_widget_class_bind_template_child (widget_class, GbpRenameFilePopover, entry);
+  gtk_widget_class_bind_template_child (widget_class, GbpRenameFilePopover, label);
+  gtk_widget_class_bind_template_child (widget_class, GbpRenameFilePopover, message);
 }
 
 static void
-gb_rename_file_popover_init (GbRenameFilePopover *self)
+gbp_rename_file_popover_init (GbpRenameFilePopover *self)
 {
   gtk_widget_init_template (GTK_WIDGET (self));
 
   g_signal_connect_object (self->entry,
                            "changed",
-                           G_CALLBACK (gb_rename_file_popover__entry_changed),
+                           G_CALLBACK (gbp_rename_file_popover__entry_changed),
                            self,
                            G_CONNECT_SWAPPED);
 
   g_signal_connect_object (self->entry,
                            "activate",
-                           G_CALLBACK (gb_rename_file_popover__entry_activate),
+                           G_CALLBACK (gbp_rename_file_popover__entry_activate),
                            self,
                            G_CONNECT_SWAPPED);
 
   g_signal_connect_object (self->button,
                            "clicked",
-                           G_CALLBACK (gb_rename_file_popover__button_clicked),
+                           G_CALLBACK (gbp_rename_file_popover__button_clicked),
                            self,
                            G_CONNECT_SWAPPED);
 
   g_signal_connect_object (self->entry,
                            "focus-in-event",
-                           G_CALLBACK (gb_rename_file_popover__entry_focus_in_event),
+                           G_CALLBACK (gbp_rename_file_popover__entry_focus_in_event),
                            self,
                            G_CONNECT_SWAPPED | G_CONNECT_AFTER);
 }
+
+void
+gbp_rename_file_popover_display_async (GbpRenameFilePopover *self,
+                                       IdeTree              *tree,
+                                       IdeTreeNode          *node,
+                                       GCancellable         *cancellable,
+                                       GAsyncReadyCallback   callback,
+                                       gpointer              user_data)
+{
+  g_autoptr(IdeTask) task = NULL;
+
+  g_return_if_fail (IDE_IS_MAIN_THREAD ());
+  g_return_if_fail (GBP_IS_RENAME_FILE_POPOVER (self));
+  g_return_if_fail (IDE_IS_TREE (tree));
+  g_return_if_fail (IDE_IS_TREE_NODE (node));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, gbp_rename_file_popover_display_async);
+
+  if (self->task != NULL)
+    {
+      ide_task_return_new_error (task,
+                                 G_IO_ERROR,
+                                 G_IO_ERROR_FAILED,
+                                 "Already displayed popover");
+      return;
+    }
+
+  self->task = g_steal_pointer (&task);
+
+  ide_tree_show_popover_at_node (tree, node, GTK_POPOVER (self));
+}
+
+GFile *
+gbp_rename_file_popover_display_finish (GbpRenameFilePopover  *self,
+                                        GAsyncResult          *result,
+                                        GError               **error)
+{
+  g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
+  g_return_val_if_fail (GBP_IS_RENAME_FILE_POPOVER (self), NULL);
+  g_return_val_if_fail (IDE_IS_TASK (result), NULL);
+
+  return ide_task_propagate_pointer (IDE_TASK (result), error);
+}
diff --git a/src/plugins/project-tree/gbp-rename-file-popover.h 
b/src/plugins/project-tree/gbp-rename-file-popover.h
new file mode 100644
index 000000000..a7897e56d
--- /dev/null
+++ b/src/plugins/project-tree/gbp-rename-file-popover.h
@@ -0,0 +1,42 @@
+/* gbp-rename-file-popover.h
+ *
+ * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ *
+ * 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
+ */
+
+#pragma once
+
+#include <libide-tree.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_RENAME_FILE_POPOVER (gbp_rename_file_popover_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpRenameFilePopover, gbp_rename_file_popover, GBP, RENAME_FILE_POPOVER, GtkPopover)
+
+GFile *gbp_rename_file_popover_get_file       (GbpRenameFilePopover  *self);
+void   gbp_rename_file_popover_display_async  (GbpRenameFilePopover  *self,
+                                               IdeTree               *tree,
+                                               IdeTreeNode           *node,
+                                               GCancellable          *cancellable,
+                                               GAsyncReadyCallback    callback,
+                                               gpointer               user_data);
+GFile *gbp_rename_file_popover_display_finish (GbpRenameFilePopover  *self,
+                                               GAsyncResult          *result,
+                                               GError               **error);
+
+G_END_DECLS
diff --git a/src/plugins/project-tree/gb-rename-file-popover.ui 
b/src/plugins/project-tree/gbp-rename-file-popover.ui
similarity index 97%
rename from src/plugins/project-tree/gb-rename-file-popover.ui
rename to src/plugins/project-tree/gbp-rename-file-popover.ui
index dbdb52d40..9da58ddc6 100644
--- a/src/plugins/project-tree/gb-rename-file-popover.ui
+++ b/src/plugins/project-tree/gbp-rename-file-popover.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <!-- interface-requires gtk+ 3.16 -->
-  <template class="GbRenameFilePopover" parent="GtkPopover">
+  <template class="GbpRenameFilePopover" parent="GtkPopover">
     <child>
       <object class="GtkBox">
         <property name="border-width">12</property>
diff --git a/src/plugins/project-tree/gtk/menus.ui b/src/plugins/project-tree/gtk/menus.ui
index 903289782..519e139ef 100644
--- a/src/plugins/project-tree/gtk/menus.ui
+++ b/src/plugins/project-tree/gtk/menus.ui
@@ -1,107 +1,85 @@
-<?xml version="1.0"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <menu id="ide-source-view-popup-menu">
-    <section id="ide-source-view-popup-menu-reveal-section">
+  <menu id="project-tree-menu">
+    <section id="project-tree-menu-placeholder0"/>
+    <section id="project-tree-menu-new-section">
       <item>
-        <attribute name="label" translatable="yes">Re_veal in Project Tree</attribute>
-        <attribute name="action">project-tree.reveal</attribute>
-      </item>
-    </section>
-  </menu>
-  <menu id="gb-project-tree-popup-menu">
-    <section id="gb-project-tree-new-section">
-      <item>
-        <attribute name="label" translatable="yes">New _File</attribute>
+        <attribute name="id">project-tree-menu-new-file</attribute>
+        <attribute name="label" translatable="yes">New File…</attribute>
         <attribute name="action">project-tree.new-file</attribute>
       </item>
       <item>
-        <attribute name="label" translatable="yes">_New Folder</attribute>
-        <attribute name="action">project-tree.new-directory</attribute>
+        <attribute name="id">project-tree-menu-new-folder</attribute>
+        <attribute name="label" translatable="yes">New Folder…</attribute>
+        <attribute name="action">project-tree.new-folder</attribute>
       </item>
     </section>
-    <section id="gb-project-tree-open-section">
+    <section id="project-tree-menu-placeholder1"/>
+    <section id="project-tree-menu-open-section">
       <item>
-        <attribute name="label" translatable="yes">_Open</attribute>
+        <attribute name="id">project-tree-menu-open</attribute>
+        <attribute name="label" translatable="yes">Open</attribute>
         <attribute name="action">project-tree.open</attribute>
-        <attribute name="accel">Return</attribute>
       </item>
-      <submenu id="gb-project-tree-open-with-submenu">
-        <attribute name="label" translatable="yes">Open _With</attribute>
-        <section id="gb-project-tree-open-with-internal-section">
+      <submenu id="project-tree-menu-open-with-menu">
+        <attribute name="label" translatable="yes">Open With…</attribute>
+        <section id="project-tree-menu-open-with-section">
           <item>
-            <attribute name="id">gb-project-tree-open-with-editor</attribute>
+            <attribute name="id">project-tree-menu-open-editor</attribute>
             <attribute name="label" translatable="yes">Source Code Editor</attribute>
             <attribute name="action">project-tree.open-with-hint</attribute>
-            <attribute name="target" type="s">"editor"</attribute>
+            <attribute name="target" type="s">'editor'</attribute>
+          </item>
+          <item>
+            <attribute name="id">project-tree-menu-open-editor</attribute>
+            <attribute name="label" translatable="yes">UI Designer</attribute>
+            <attribute name="action">project-tree.open-with-hint</attribute>
+            <attribute name="target" type="s">'glade'</attribute>
           </item>
-        </section>
-        <section id="gb-project-tree-open-by-mime-section">
         </section>
       </submenu>
-    </section>
-    <section id="gb-project-tree-open-containing-section">
       <item>
-        <attribute name="label" translatable="yes">_Open Containing Folder</attribute>
+        <attribute name="id">project-tree-menu-open-folder</attribute>
+        <attribute name="label" translatable="yes">Open Containing Folder</attribute>
         <attribute name="action">project-tree.open-containing-folder</attribute>
       </item>
       <item>
-        <attribute name="label" translatable="yes">_Open in Terminal</attribute>
+        <attribute name="id">project-tree-menu-open-terminal</attribute>
+        <attribute name="label" translatable="yes">Open in Terminal</attribute>
         <attribute name="action">project-tree.open-in-terminal</attribute>
       </item>
     </section>
-    <section id="gb-project-tree-find-section"/>
-    <section id="gb-project-tree-rename-section">
-      <item>
-        <attribute name="label" translatable="yes">_Rename</attribute>
-        <attribute name="action">project-tree.rename-file</attribute>
-        <attribute name="accel">F2</attribute>
-      </item>
-    </section>
-    <section id="gb-project-tree-move-to-trash-section">
-      <item>
-        <attribute name="label" translatable="yes">Mo_ve to Trash</attribute>
-        <attribute name="action">project-tree.move-to-trash</attribute>
-        <attribute name="accel">Delete</attribute>
-      </item>
-    </section>
-    <section id="gb-project-tree-build-section">
+    <section id="project-tree-menu-placeholder2"/>
+    <section id="project-tree-menu-destructive-section">
       <item>
-        <attribute name="label" translatable="yes">_Build</attribute>
-        <attribute name="action">workbench.build</attribute>
+        <attribute name="id">project-tree-menu-rename</attribute>
+        <attribute name="label" translatable="yes">Rename</attribute>
+        <attribute name="action">project-tree.rename</attribute>
       </item>
       <item>
-        <attribute name="label" translatable="yes">_Rebuild</attribute>
-        <attribute name="action">workbench.rebuild</attribute>
+        <attribute name="id">project-tree-menu-trash</attribute>
+        <attribute name="label" translatable="yes">Move to Trash</attribute>
+        <attribute name="action">project-tree.trash</attribute>
       </item>
     </section>
-    <section id="gb-project-tree-display-options-section">
-      <submenu id="gb-project-tree-display-options-submenu">
+    <section id="project-tree-menu-placeholder3"/>
+    <section id="project-tree-menu-display-options-parent-section">
+      <submenu id="project-tree-menu-display-options">
         <attribute name="label" translatable="yes">Display Options</attribute>
-        <section id="gb-project-tree-display-options-show-section">
-          <item>
-            <attribute name="label" translatable="yes">Show Icons</attribute>
-            <attribute name="action">project-tree.show-icons</attribute>
-          </item>
+        <section id="project-tree-menu-display-options-section">
           <item>
+            <attribute name="id">project-tree-menu-show-ignored</attribute>
             <attribute name="label" translatable="yes">Show Ignored Files</attribute>
             <attribute name="action">project-tree.show-ignored-files</attribute>
           </item>
           <item>
+            <attribute name="id">project-tree-menu-sort-directories-first</attribute>
             <attribute name="label" translatable="yes">Sort Directories First</attribute>
             <attribute name="action">project-tree.sort-directories-first</attribute>
           </item>
         </section>
-        <section id="gb-project-tree-display-options-nodes-section">
-          <item>
-            <attribute name="label" translatable="yes">_Collapse All Nodes</attribute>
-            <attribute name="action">project-tree.collapse-all-nodes</attribute>
-          </item>
-          <item>
-            <attribute name="label" translatable="yes">_Refresh</attribute>
-            <attribute name="action">project-tree.refresh</attribute>
-          </item>
-        </section>
       </submenu>
     </section>
+    <section id="project-tree-menu-placeholder4"/>
   </menu>
 </interface>
diff --git a/src/plugins/project-tree/meson.build b/src/plugins/project-tree/meson.build
index 4ca024259..c95aa92be 100644
--- a/src/plugins/project-tree/meson.build
+++ b/src/plugins/project-tree/meson.build
@@ -1,39 +1,18 @@
-if get_option('with_project_tree')
+plugins_sources += files([
+  'project-tree-plugin.c',
+  'gbp-project-tree.c',
+  'gbp-project-tree-addin.c',
+  'gbp-project-tree-pane.c',
+  'gbp-project-tree-pane-actions.c',
+  'gbp-project-tree-workspace-addin.c',
+  'gbp-new-file-popover.c',
+  'gbp-rename-file-popover.c',
+])
 
-project_tree_resources = gnome.compile_resources(
-  'project-tree-resources',
+plugin_project_tree_resources = gnome.compile_resources(
+  'gbp-project-tree-resources',
   'project-tree.gresource.xml',
-  c_name: 'gb_project_tree',
+  c_name: 'gbp_project_tree',
 )
 
-project_tree_sources = [
-  'gb-new-file-popover.c',
-  'gb-new-file-popover.h',
-  'gb-project-file.c',
-  'gb-project-file.h',
-  'gb-project-tree-actions.c',
-  'gb-project-tree-actions.h',
-  'gb-project-tree-builder.c',
-  'gb-project-tree-builder.h',
-  'gb-project-tree.c',
-  'gb-project-tree.h',
-  'gb-project-tree-editor-addin.c',
-  'gb-project-tree-editor-addin.h',
-  'gb-project-tree-private.h',
-  'gb-project-tree-shortcuts.c',
-  'gb-rename-file-popover.c',
-  'gb-rename-file-popover.h',
-  'gb-project-tree-addin.c',
-  'gb-project-tree-addin.h',
-  'gb-vcs-tree-builder.c',
-  'gb-vcs-tree-builder.h',
-  'project-tree-plugin.c',
-]
-
-gnome_builder_plugins_deps += dependency('vte-2.91', version: '>=0.40.2')
-gnome_builder_plugins_args += '-DHAVE_VTE'
-
-gnome_builder_plugins_sources += files(project_tree_sources)
-gnome_builder_plugins_sources += project_tree_resources[0]
-
-endif
+plugins_sources += plugin_project_tree_resources[0]
diff --git a/src/plugins/project-tree/project-tree-plugin.c b/src/plugins/project-tree/project-tree-plugin.c
index 97f7aa38a..1221ebadd 100644
--- a/src/plugins/project-tree/project-tree-plugin.c
+++ b/src/plugins/project-tree/project-tree-plugin.c
@@ -18,19 +18,24 @@
  * SPDX-License-Identifier: GPL-3.0-or-later
  */
 
+#define G_LOG_DOMAIN "project-tree-plugin"
+
+#include "config.h"
+
+#include <libide-gui.h>
+#include <libide-tree.h>
 #include <libpeas/peas.h>
-#include <ide.h>
 
-#include "gb-project-tree-addin.h"
-#include "gb-project-tree-editor-addin.h"
+#include "gbp-project-tree-addin.h"
+#include "gbp-project-tree-workspace-addin.h"
 
 void
-gb_project_tree_register_types (PeasObjectModule *module)
+_gbp_project_tree_register_types (PeasObjectModule *module)
 {
   peas_object_module_register_extension_type (module,
-                                              IDE_TYPE_WORKBENCH_ADDIN,
-                                              GB_TYPE_PROJECT_TREE_ADDIN);
+                                              IDE_TYPE_TREE_ADDIN,
+                                              GBP_TYPE_PROJECT_TREE_ADDIN);
   peas_object_module_register_extension_type (module,
-                                              IDE_TYPE_EDITOR_VIEW_ADDIN,
-                                              GB_TYPE_PROJECT_TREE_EDITOR_ADDIN);
+                                              IDE_TYPE_WORKSPACE_ADDIN,
+                                              GBP_TYPE_PROJECT_TREE_WORKSPACE_ADDIN);
 }
diff --git a/src/plugins/project-tree/project-tree.gresource.xml 
b/src/plugins/project-tree/project-tree.gresource.xml
index 0c6b165b8..075b14326 100644
--- a/src/plugins/project-tree/project-tree.gresource.xml
+++ b/src/plugins/project-tree/project-tree.gresource.xml
@@ -1,12 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <gresources>
-  <gresource prefix="/org/gnome/builder/plugins">
+  <gresource prefix="/plugins/project-tree">
+    <file preprocess="xml-stripblanks">gtk/menus.ui</file>
+    <file preprocess="xml-stripblanks">gbp-project-tree-pane.ui</file>
+    <file preprocess="xml-stripblanks">gbp-new-file-popover.ui</file>
+    <file preprocess="xml-stripblanks">gbp-rename-file-popover.ui</file>
     <file>project-tree.plugin</file>
-  </gresource>
-  <gresource prefix="/org/gnome/builder/plugins/project-tree-plugin">
-    <file>gb-new-file-popover.ui</file>
-    <file>gb-rename-file-popover.ui</file>
-    <file>gtk/menus.ui</file>
     <file>themes/shared.css</file>
   </gresource>
 </gresources>
diff --git a/src/plugins/project-tree/project-tree.plugin b/src/plugins/project-tree/project-tree.plugin
index c8dd44a06..8e7566092 100644
--- a/src/plugins/project-tree/project-tree.plugin
+++ b/src/plugins/project-tree/project-tree.plugin
@@ -1,10 +1,12 @@
 [Plugin]
-Module=project-tree-plugin
-Name=Project Tree
-Description=Provides a project tree
 Authors=Christian Hergert <christian hergert me>
-Copyright=Copyright © 2015 Christian Hergert
-Depends=editor
 Builtin=true
+Copyright=Copyright © 2014-2018 Christian Hergert
+Depends=editor;
+Description=Builder's project creation wizard
+Embedded=_gbp_project_tree_register_types
 Hidden=true
-Embedded=gb_project_tree_register_types
+Module=project-tree
+Name=Project Tree
+X-Workspace-Kind=primary;
+X-Tree-Kind=project-tree;
diff --git a/src/plugins/project-tree/themes/shared.css b/src/plugins/project-tree/themes/shared.css
index 79880fb6d..a6c2bd476 100644
--- a/src/plugins/project-tree/themes/shared.css
+++ b/src/plugins/project-tree/themes/shared.css
@@ -5,3 +5,11 @@ ideeditorsidebar treeview.project-tree {
   -gtk-icon-source: none;
   padding-left: 6px;
 }
+
+.vcs-added {
+  color: @success_color;
+}
+
+.vcs-changed {
+  color: @warning_color;
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]