[gnome-builder] ls: add simple directory view



commit 45707133b9735434878ba9494b2dd0403ae427e3
Author: Christian Hergert <chergert redhat com>
Date:   Tue Oct 30 22:47:58 2018 -0700

    ls: add simple directory view
    
    This is handy for users of vim that are used to being able to open a
    directory and get a list of files. You can quickly type-ahead search and
    press enter to open the target file.

 meson_options.txt                       |   1 +
 po/POTFILES.in                          |   2 +
 src/plugins/ls/gbp-ls-model.c           | 546 ++++++++++++++++++++++++++++++++
 src/plugins/ls/gbp-ls-model.h           |  44 +++
 src/plugins/ls/gbp-ls-plugin.c          |  34 ++
 src/plugins/ls/gbp-ls-view.c            | 353 +++++++++++++++++++++
 src/plugins/ls/gbp-ls-view.h            |  36 +++
 src/plugins/ls/gbp-ls-view.ui           |  74 +++++
 src/plugins/ls/gbp-ls-workbench-addin.c | 179 +++++++++++
 src/plugins/ls/gbp-ls-workbench-addin.h |  31 ++
 src/plugins/ls/ls.gresource.xml         |   9 +
 src/plugins/ls/ls.plugin                |   9 +
 src/plugins/ls/meson.build              |  19 ++
 src/plugins/meson.build                 |   2 +
 14 files changed, 1339 insertions(+)
---
diff --git a/meson_options.txt b/meson_options.txt
index ae5c8d73a..68fe4227c 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -58,6 +58,7 @@ option('with_html_completion', type: 'boolean')
 option('with_html_preview', type: 'boolean')
 option('with_jedi', type: 'boolean')
 option('with_jhbuild', type: 'boolean')
+option('with_ls', type: 'boolean')
 option('with_make', type: 'boolean')
 option('with_maven', type: 'boolean')
 option('with_meson', type: 'boolean')
diff --git a/po/POTFILES.in b/po/POTFILES.in
index e319b7c52..e95b49d10 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -202,6 +202,8 @@ src/plugins/grep/gtk/menus.ui
 src/plugins/html-preview/gtk/menus.ui
 src/plugins/html-preview/html_preview.py
 src/plugins/jedi/jedi_plugin.py
+src/plugins/ls/gbp-ls-view.c
+src/plugins/ls/gbp-ls-view.ui
 src/plugins/make/make_plugin.py
 src/plugins/maven/maven_plugin.py
 src/plugins/meson/gbp-meson-build-system.c
diff --git a/src/plugins/ls/gbp-ls-model.c b/src/plugins/ls/gbp-ls-model.c
new file mode 100644
index 000000000..252b0b756
--- /dev/null
+++ b/src/plugins/ls/gbp-ls-model.c
@@ -0,0 +1,546 @@
+/* gbp-ls-model.c
+ *
+ * Copyright © 2018 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
+ */
+
+#include "config.h"
+
+#define G_LOG_DOMAIN "gbp-ls-model"
+
+#include <ide.h>
+
+#include "gbp-ls-model.h"
+
+struct _GbpLsModel
+{
+  GObject    parent_instance;
+  GFile     *directory;
+  GPtrArray *items;
+};
+
+enum {
+  PROP_0,
+  PROP_DIRECTORY,
+  N_PROPS
+};
+
+static void async_initable_iface_init (GAsyncInitableIface *iface);
+static void tree_model_iface_init     (GtkTreeModelIface   *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GbpLsModel, gbp_ls_model, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init)
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, tree_model_iface_init))
+
+static GParamSpec *properties [N_PROPS];
+
+static gint
+compare_by_name (gconstpointer a,
+                 gconstpointer b)
+{
+  GFileInfo *info_a = *(GFileInfo **)a;
+  GFileInfo *info_b = *(GFileInfo **)b;
+  GFileType type_a = g_file_info_get_file_type (info_a);
+  GFileType type_b = g_file_info_get_file_type (info_b);
+  const gchar *display_a;
+  const gchar *display_b;
+
+  if (type_a != type_b)
+    {
+      if (type_a == G_FILE_TYPE_DIRECTORY)
+        return -1;
+      else if (type_b == G_FILE_TYPE_DIRECTORY)
+        return 1;
+    }
+
+  display_a = g_file_info_get_display_name (info_a);
+  display_b = g_file_info_get_display_name (info_b);
+
+  if (g_strcmp0 (display_a, "..") == 0)
+    return -1;
+  else if (g_strcmp0 (display_b, "..") == 0)
+    return 1;
+  else
+    return g_utf8_collate (display_a, display_b);
+}
+
+static void
+gbp_ls_model_finalize (GObject *object)
+{
+  GbpLsModel *self = (GbpLsModel *)object;
+
+  g_clear_object (&self->directory);
+  g_clear_pointer (&self->items, g_ptr_array_unref);
+
+  G_OBJECT_CLASS (gbp_ls_model_parent_class)->finalize (object);
+}
+
+static void
+gbp_ls_model_get_property (GObject    *object,
+                           guint       prop_id,
+                           GValue     *value,
+                           GParamSpec *pspec)
+{
+  GbpLsModel *self = GBP_LS_MODEL (object);
+
+  switch (prop_id)
+    {
+    case PROP_DIRECTORY:
+      g_value_set_object (value, self->directory);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_ls_model_set_property (GObject      *object,
+                           guint         prop_id,
+                           const GValue *value,
+                           GParamSpec   *pspec)
+{
+  GbpLsModel *self = GBP_LS_MODEL (object);
+
+  switch (prop_id)
+    {
+    case PROP_DIRECTORY:
+      self->directory = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_ls_model_class_init (GbpLsModelClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gbp_ls_model_finalize;
+  object_class->get_property = gbp_ls_model_get_property;
+  object_class->set_property = gbp_ls_model_set_property;
+
+  properties [PROP_DIRECTORY] =
+    g_param_spec_object ("directory",
+                         "Directory",
+                         "The directory to display contents from",
+                         G_TYPE_FILE,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gbp_ls_model_init (GbpLsModel *self)
+{
+  self->items = g_ptr_array_new_with_free_func (g_object_unref);
+}
+
+static gint
+gbp_ls_model_get_n_columns (GtkTreeModel *model)
+{
+  return GBP_LS_MODEL_N_COLUMNS;
+}
+
+static GType
+gbp_ls_model_get_column_type (GtkTreeModel *model,
+                              gint          column)
+{
+  switch (column)
+    {
+    case GBP_LS_MODEL_COLUMN_GICON:    return G_TYPE_ICON;
+    case GBP_LS_MODEL_COLUMN_NAME:     return G_TYPE_STRING;
+    case GBP_LS_MODEL_COLUMN_SIZE:     return G_TYPE_INT64;
+    case GBP_LS_MODEL_COLUMN_MODIFIED: return G_TYPE_DATE_TIME;
+    case GBP_LS_MODEL_COLUMN_FILE:     return G_TYPE_FILE;
+    case GBP_LS_MODEL_N_COLUMNS:
+    default:
+      g_return_val_if_reached (G_TYPE_INVALID);
+    }
+}
+
+static gboolean
+gbp_ls_model_get_iter (GtkTreeModel *model,
+                       GtkTreeIter  *iter,
+                       GtkTreePath  *path)
+{
+  GbpLsModel *self = (GbpLsModel *)model;
+  gint *indicies;
+  gint depth = 0;
+
+  g_assert (GBP_IS_LS_MODEL (model));
+  g_assert (iter != NULL);
+
+  indicies = gtk_tree_path_get_indices_with_depth (path, &depth);
+
+  if (depth == 1 && indicies[0] < self->items->len)
+    {
+      iter->user_data = g_ptr_array_index (self->items, indicies[0]);
+      iter->user_data2 = GUINT_TO_POINTER (indicies[0]);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+gbp_ls_model_get_value (GtkTreeModel *model,
+                        GtkTreeIter  *iter,
+                        gint          column,
+                        GValue       *value)
+{
+  GbpLsModel *self = (GbpLsModel *)model;
+  GFileInfo *info;
+
+  g_assert (GBP_IS_LS_MODEL (self));
+  g_assert (iter != NULL);
+  g_assert (value != NULL);
+
+  info = iter->user_data;
+
+  g_assert (G_IS_FILE_INFO (info));
+
+  switch (column)
+    {
+    case GBP_LS_MODEL_COLUMN_GICON:
+      g_value_init (value, G_TYPE_ICON);
+      g_value_set_object (value,
+                          g_file_info_get_attribute_object (info,
+                                                            G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON));
+      break;
+
+    case GBP_LS_MODEL_COLUMN_NAME:
+      g_value_init (value, G_TYPE_STRING);
+      g_value_set_string (value, g_file_info_get_display_name (info));
+      break;
+
+    case GBP_LS_MODEL_COLUMN_SIZE:
+      g_value_init (value, G_TYPE_INT64);
+      g_value_set_int64 (value, g_file_info_get_size (info));
+      break;
+
+    case GBP_LS_MODEL_COLUMN_MODIFIED:
+      {
+        GTimeVal tv;
+
+        g_file_info_get_modification_time (info, &tv);
+        g_value_init (value, G_TYPE_DATE_TIME);
+        g_value_take_boxed (value, g_date_time_new_from_timeval_local (&tv));
+
+        break;
+      }
+
+    case GBP_LS_MODEL_COLUMN_FILE:
+      {
+        g_value_init (value, G_TYPE_FILE);
+        g_value_take_object (value,
+                             g_file_get_child (self->directory,
+                                               g_file_info_get_name (info)));
+        break;
+      }
+
+    case GBP_LS_MODEL_COLUMN_TYPE:
+      {
+        g_value_init (value, G_TYPE_FILE_TYPE);
+        g_value_set_enum (value, g_file_info_get_file_type (info));
+        break;
+      }
+
+    case GBP_LS_MODEL_N_COLUMNS:
+    default:
+      g_return_if_reached ();
+    }
+}
+
+static gint
+gbp_ls_model_iter_n_children (GtkTreeModel *model,
+                              GtkTreeIter  *iter)
+{
+  GbpLsModel *self = (GbpLsModel *)model;
+
+  g_assert (GBP_IS_LS_MODEL (self));
+
+  return iter ? 0 : self->items->len;
+}
+
+static gboolean
+gbp_ls_model_iter_children (GtkTreeModel *model,
+                            GtkTreeIter  *iter,
+                            GtkTreeIter  *parent)
+{
+  GbpLsModel *self = (GbpLsModel *)model;
+
+  g_assert (GBP_IS_LS_MODEL (self));
+
+  if (iter != NULL || self->items->len == 0)
+    return FALSE;
+
+  iter->user_data = g_ptr_array_index (self->items, 0);
+  iter->user_data2 = GUINT_TO_POINTER (0);
+
+  return TRUE;
+}
+
+static gboolean
+gbp_ls_model_iter_next (GtkTreeModel *model,
+                        GtkTreeIter  *iter)
+{
+  GbpLsModel *self = (GbpLsModel *)model;
+  guint index_;
+
+  g_assert (GBP_IS_LS_MODEL (self));
+  g_assert (iter != NULL);
+
+  index_ = GPOINTER_TO_UINT (iter->user_data2);
+
+  if (index_ != G_MAXUINT && index_ + 1 < self->items->len)
+    {
+      index_++;
+
+      iter->user_data = g_ptr_array_index (self->items, index_);
+      iter->user_data2 = GUINT_TO_POINTER (index_);
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+gbp_ls_model_iter_nth_child (GtkTreeModel *model,
+                             GtkTreeIter  *iter,
+                             GtkTreeIter  *parent,
+                             gint          n)
+{
+  GbpLsModel *self = (GbpLsModel *)model;
+
+  g_assert (GBP_IS_LS_MODEL (model));
+  g_assert (iter != NULL);
+
+  if (parent != NULL)
+    return FALSE;
+
+  if (n < self->items->len)
+    {
+      iter->user_data = g_ptr_array_index (self->items, n);
+      iter->user_data2 = GINT_TO_POINTER (n);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static GtkTreePath *
+gbp_ls_model_get_path (GtkTreeModel *model,
+                       GtkTreeIter  *iter)
+{
+  g_assert (GBP_IS_LS_MODEL (model));
+  g_assert (iter != NULL);
+
+  return gtk_tree_path_new_from_indices (GPOINTER_TO_UINT (iter->user_data2), -1);
+}
+
+static gboolean
+gbp_ls_model_iter_has_child (GtkTreeModel *model,
+                             GtkTreeIter  *parent)
+{
+  GbpLsModel *self = (GbpLsModel *)model;
+
+  g_assert (GBP_IS_LS_MODEL (self));
+
+  return parent ? 0 : self->items->len > 0;
+}
+
+static void
+tree_model_iface_init (GtkTreeModelIface *iface)
+{
+  iface->get_n_columns = gbp_ls_model_get_n_columns;
+  iface->get_iter = gbp_ls_model_get_iter;
+  iface->get_column_type = gbp_ls_model_get_column_type;
+  iface->get_value = gbp_ls_model_get_value;
+  iface->iter_n_children = gbp_ls_model_iter_n_children;
+  iface->iter_children = gbp_ls_model_iter_children;
+  iface->iter_next = gbp_ls_model_iter_next;
+  iface->iter_nth_child = gbp_ls_model_iter_nth_child;
+  iface->iter_has_child = gbp_ls_model_iter_has_child;
+  iface->get_path = gbp_ls_model_get_path;
+}
+
+static void
+gbp_ls_model_worker (IdeTask      *task,
+                     gpointer      source_object,
+                     gpointer      task_data,
+                     GCancellable *cancellable)
+{
+  g_autoptr(GFileEnumerator) enumerator = NULL;
+  g_autoptr(GPtrArray) items = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GIcon) icon = NULL;
+  g_autoptr(GFile) parent = NULL;
+  GFile *directory = task_data;
+  GFileInfo *info;
+
+  g_assert (IDE_IS_TASK (task));
+  g_assert (G_IS_FILE (directory));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  enumerator = g_file_enumerate_children (directory,
+                                          G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME","
+                                          G_FILE_ATTRIBUTE_STANDARD_SIZE","
+                                          G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON","
+                                          G_FILE_ATTRIBUTE_TIME_MODIFIED",",
+                                          G_FILE_QUERY_INFO_NONE,
+                                          cancellable,
+                                          &error);
+
+  if (enumerator == NULL)
+    {
+      ide_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  /* Prefer folder-symbolic over queried icon */
+  icon = g_themed_icon_new ("folder-symbolic");
+
+  items = g_ptr_array_new_with_free_func (g_object_unref);
+
+  if ((parent = g_file_get_parent (directory)))
+    {
+      g_autoptr(GFileInfo) dot = NULL;
+
+      dot = g_file_query_info (parent,
+                               G_FILE_ATTRIBUTE_STANDARD_SIZE","
+                               G_FILE_ATTRIBUTE_TIME_MODIFIED",",
+                               G_FILE_QUERY_INFO_NONE,
+                               cancellable,
+                               NULL);
+
+      if (dot != NULL)
+        {
+          g_file_info_set_name (dot, "..");
+          g_file_info_set_display_name (dot, "..");
+          g_file_info_set_file_type (dot, G_FILE_TYPE_DIRECTORY);
+          g_file_info_set_attribute_object (dot,
+                                            G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON,
+                                            G_OBJECT (icon));
+          g_ptr_array_add (items, g_steal_pointer (&dot));
+        }
+    }
+
+  while ((info = g_file_enumerator_next_file (enumerator, cancellable, &error)))
+    {
+      GFileType file_type = g_file_info_get_file_type (info);
+
+      /* Prefer our symbolic icon for folders */
+      if (file_type == G_FILE_TYPE_DIRECTORY)
+        g_file_info_set_attribute_object (info, G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON, G_OBJECT (icon));
+
+      g_ptr_array_add (items, info);
+    }
+
+  if (error != NULL)
+    {
+      ide_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  g_ptr_array_sort (items, compare_by_name);
+
+  ide_task_return_pointer (task,
+                           g_steal_pointer (&items),
+                           (GDestroyNotify)g_ptr_array_unref);
+}
+
+static void
+gbp_ls_model_init_async (GAsyncInitable      *initable,
+                         gint                 io_priority,
+                         GCancellable        *cancellable,
+                         GAsyncReadyCallback  callback,
+                         gpointer             user_data)
+{
+  GbpLsModel *self = (GbpLsModel *)initable;
+  g_autoptr(IdeTask) task = NULL;
+
+  g_assert (GBP_IS_LS_MODEL (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, gbp_ls_model_init_async);
+  ide_task_set_priority (task, io_priority);
+
+  if (self->directory == NULL)
+    {
+      ide_task_return_new_error (task,
+                                 G_IO_ERROR,
+                                 G_IO_ERROR_INVALID_FILENAME,
+                                 "No directory found to enumerate");
+      return;
+    }
+
+  ide_task_set_task_data (task, g_object_ref (self->directory), g_object_unref);
+  ide_task_run_in_thread (task, gbp_ls_model_worker);
+}
+
+static gboolean
+gbp_ls_model_init_finish (GAsyncInitable  *initable,
+                          GAsyncResult    *result,
+                          GError         **error)
+{
+  g_autoptr(GPtrArray) items = NULL;
+  GbpLsModel *self = (GbpLsModel *)initable;
+
+  g_assert (GBP_IS_LS_MODEL (initable));
+  g_assert (IDE_IS_TASK (result));
+
+  items = ide_task_propagate_pointer (IDE_TASK (result), error);
+
+  if (items != NULL)
+    {
+      g_clear_pointer (&self->items, g_ptr_array_unref);
+      self->items = g_steal_pointer (&items);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+async_initable_iface_init (GAsyncInitableIface *iface)
+{
+  iface->init_async = gbp_ls_model_init_async;
+  iface->init_finish = gbp_ls_model_init_finish;
+}
+
+GbpLsModel *
+gbp_ls_model_new (GFile *directory)
+{
+  g_return_val_if_fail (G_IS_FILE (directory), NULL);
+
+  return g_object_new (GBP_TYPE_LS_MODEL,
+                       "directory", directory,
+                       NULL);
+}
+
+GFile *
+gbp_ls_model_get_directory (GbpLsModel *self)
+{
+  g_return_val_if_fail (GBP_IS_LS_MODEL (self), NULL);
+
+  return self->directory;
+}
diff --git a/src/plugins/ls/gbp-ls-model.h b/src/plugins/ls/gbp-ls-model.h
new file mode 100644
index 000000000..00993a761
--- /dev/null
+++ b/src/plugins/ls/gbp-ls-model.h
@@ -0,0 +1,44 @@
+/* gbp-ls-model.h
+ *
+ * Copyright © 2018 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
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_LS_MODEL (gbp_ls_model_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpLsModel, gbp_ls_model, GBP, LS_MODEL, GObject)
+
+typedef enum {
+  GBP_LS_MODEL_COLUMN_GICON,
+  GBP_LS_MODEL_COLUMN_NAME,
+  GBP_LS_MODEL_COLUMN_SIZE,
+  GBP_LS_MODEL_COLUMN_MODIFIED,
+  GBP_LS_MODEL_COLUMN_FILE,
+  GBP_LS_MODEL_COLUMN_TYPE,
+  GBP_LS_MODEL_N_COLUMNS
+} GbpLsModelColumn;
+
+GbpLsModel *gbp_ls_model_new           (GFile      *directory);
+GFile      *gbp_ls_model_get_directory (GbpLsModel *self);
+
+G_END_DECLS
diff --git a/src/plugins/ls/gbp-ls-plugin.c b/src/plugins/ls/gbp-ls-plugin.c
new file mode 100644
index 000000000..b0842a515
--- /dev/null
+++ b/src/plugins/ls/gbp-ls-plugin.c
@@ -0,0 +1,34 @@
+/* gbp-ls-plugin.c
+ *
+ * Copyright © 2018 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
+ */
+
+#include "config.h"
+
+#include <libpeas/peas.h>
+#include <ide.h>
+
+#include "gbp-ls-workbench-addin.h"
+
+void
+gbp_ls_register_types (PeasObjectModule *module)
+{
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_WORKBENCH_ADDIN,
+                                              GBP_TYPE_LS_WORKBENCH_ADDIN);
+}
diff --git a/src/plugins/ls/gbp-ls-view.c b/src/plugins/ls/gbp-ls-view.c
new file mode 100644
index 000000000..0040b5ec9
--- /dev/null
+++ b/src/plugins/ls/gbp-ls-view.c
@@ -0,0 +1,353 @@
+/* gbp-ls-view.c
+ *
+ * Copyright © 2018 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
+ */
+
+#include "config.h"
+
+#define G_LOG_DOMAIN "gbp-ls-view"
+
+#include <glib/gi18n.h>
+
+#include "gbp-ls-model.h"
+#include "gbp-ls-view.h"
+
+struct _GbpLsView
+{
+  IdeLayoutView      parent_instance;
+
+  GCancellable      *model_cancellable;
+  GbpLsModel        *model;
+
+  GtkScrolledWindow *scroller;
+  GtkTreeView       *tree_view;
+  GtkTreeViewColumn *modified_column;
+  GtkCellRenderer   *modified_cell;
+  GtkTreeViewColumn *size_column;
+  GtkCellRenderer   *size_cell;
+
+  guint              close_on_activate : 1;
+};
+
+enum {
+  PROP_0,
+  PROP_CLOSE_ON_ACTIVATE,
+  PROP_DIRECTORY,
+  N_PROPS
+};
+
+G_DEFINE_TYPE (GbpLsView, gbp_ls_view, IDE_TYPE_LAYOUT_VIEW)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+gbp_ls_view_row_activated_cb (GbpLsView         *self,
+                              GtkTreePath       *path,
+                              GtkTreeViewColumn *column,
+                              GtkTreeView       *tree_view)
+{
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+
+  g_assert (GBP_IS_LS_VIEW (self));
+  g_assert (path != NULL);
+
+  if ((model = gtk_tree_view_get_model (tree_view)) &&
+      gtk_tree_model_get_iter (model, &iter, path))
+    {
+      g_autoptr(GFile) file = NULL;
+      GFileType file_type;
+
+      gtk_tree_model_get (model, &iter,
+                          GBP_LS_MODEL_COLUMN_FILE, &file,
+                          GBP_LS_MODEL_COLUMN_TYPE, &file_type,
+                          -1);
+
+      if (file_type == G_FILE_TYPE_DIRECTORY)
+        gbp_ls_view_set_directory (self, file);
+      else
+        {
+          g_autoptr(IdeUri) uri = ide_uri_new_from_file (file);
+          IdeWorkbench *workbench = ide_widget_get_workbench (GTK_WIDGET (self));
+
+          ide_workbench_open_uri_async (workbench,
+                                        uri,
+                                        NULL,
+                                        IDE_WORKBENCH_OPEN_FLAGS_NONE,
+                                        NULL,
+                                        NULL,
+                                        NULL);
+
+          if (self->close_on_activate)
+            dzl_gtk_widget_action (GTK_WIDGET (self), "layoutstack", "close-view", NULL);
+        }
+    }
+}
+
+static void
+modified_cell_data_func (GtkCellLayout   *cell_layout,
+                         GtkCellRenderer *cell,
+                         GtkTreeModel    *tree_model,
+                         GtkTreeIter     *iter,
+                         gpointer         data)
+{
+  g_autofree gchar *format = NULL;
+  g_autoptr(GDateTime) when = NULL;
+
+  gtk_tree_model_get (tree_model, iter,
+                      GBP_LS_MODEL_COLUMN_MODIFIED, &when,
+                      -1);
+  format = dzl_g_date_time_format_for_display (when);
+  g_object_set (cell, "text", format, NULL);
+}
+
+static void
+size_cell_data_func (GtkCellLayout   *cell_layout,
+                     GtkCellRenderer *cell,
+                     GtkTreeModel    *tree_model,
+                     GtkTreeIter     *iter,
+                     gpointer         data)
+{
+  g_autofree gchar *format = NULL;
+  gint64 size = -1;
+
+  gtk_tree_model_get (tree_model, iter,
+                      GBP_LS_MODEL_COLUMN_SIZE, &size,
+                      -1);
+  format = g_format_size (size);
+  g_object_set (cell, "text", format, NULL);
+}
+
+static void
+gbp_ls_view_finalize (GObject *object)
+{
+  GbpLsView *self = (GbpLsView *)object;
+
+  g_clear_object (&self->model_cancellable);
+  g_clear_object (&self->model);
+
+  G_OBJECT_CLASS (gbp_ls_view_parent_class)->finalize (object);
+}
+
+static void
+gbp_ls_view_get_property (GObject    *object,
+                          guint       prop_id,
+                          GValue     *value,
+                          GParamSpec *pspec)
+{
+  GbpLsView *self = GBP_LS_VIEW (object);
+
+  switch (prop_id)
+    {
+    case PROP_DIRECTORY:
+      g_value_set_object (value, gbp_ls_view_get_directory (self));
+      break;
+
+    case PROP_CLOSE_ON_ACTIVATE:
+      g_value_set_boolean (value, self->close_on_activate);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_ls_view_set_property (GObject      *object,
+                          guint         prop_id,
+                          const GValue *value,
+                          GParamSpec   *pspec)
+{
+  GbpLsView *self = GBP_LS_VIEW (object);
+
+  switch (prop_id)
+    {
+    case PROP_DIRECTORY:
+      gbp_ls_view_set_directory (self, g_value_get_object (value));
+      break;
+
+    case PROP_CLOSE_ON_ACTIVATE:
+      self->close_on_activate = g_value_get_boolean (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_ls_view_class_init (GbpLsViewClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = gbp_ls_view_finalize;
+  object_class->get_property = gbp_ls_view_get_property;
+  object_class->set_property = gbp_ls_view_set_property;
+
+  properties [PROP_DIRECTORY] =
+    g_param_spec_object ("directory",
+                         "Directory",
+                         "The directory to be displayed",
+                         G_TYPE_FILE,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_CLOSE_ON_ACTIVATE] =
+    g_param_spec_boolean ("close-on-activate", NULL, NULL,
+                          FALSE,
+                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/builder/plugins/ls/gbp-ls-view.ui");
+  gtk_widget_class_bind_template_child (widget_class, GbpLsView, modified_cell);
+  gtk_widget_class_bind_template_child (widget_class, GbpLsView, modified_column);
+  gtk_widget_class_bind_template_child (widget_class, GbpLsView, size_cell);
+  gtk_widget_class_bind_template_child (widget_class, GbpLsView, size_column);
+  gtk_widget_class_bind_template_child (widget_class, GbpLsView, scroller);
+  gtk_widget_class_bind_template_child (widget_class, GbpLsView, tree_view);
+}
+
+static void
+gbp_ls_view_init (GbpLsView *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  ide_layout_view_set_icon_name (IDE_LAYOUT_VIEW (self), "folder-symbolic");
+
+  g_signal_connect_object (self->tree_view,
+                           "row-activated",
+                           G_CALLBACK (gbp_ls_view_row_activated_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->size_column),
+                                      self->size_cell,
+                                      size_cell_data_func,
+                                      NULL, NULL);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->modified_column),
+                                      self->modified_cell,
+                                      modified_cell_data_func,
+                                      NULL, NULL);
+}
+
+GtkWidget *
+gbp_ls_view_new (void)
+{
+  return g_object_new (GBP_TYPE_LS_VIEW, NULL);
+}
+
+GFile *
+gbp_ls_view_get_directory (GbpLsView *self)
+{
+  g_return_val_if_fail (GBP_IS_LS_VIEW (self), NULL);
+
+  if (self->model != NULL)
+    return gbp_ls_model_get_directory (GBP_LS_MODEL (self->model));
+
+  return NULL;
+}
+
+static void
+gbp_ls_view_init_model_cb (GObject      *object,
+                           GAsyncResult *result,
+                           gpointer      user_data)
+{
+  GbpLsModel *model = (GbpLsModel *)object;
+  g_autoptr(GbpLsView) self = user_data;
+  g_autoptr(GError) error = NULL;
+  GtkTreeIter iter;
+
+  g_assert (GBP_IS_LS_MODEL (model));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (GBP_IS_LS_VIEW (self));
+
+  if (model != self->model)
+    return;
+
+  if (!g_async_initable_init_finish (G_ASYNC_INITABLE (model), result, &error))
+    {
+      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        ide_layout_view_report_error (IDE_LAYOUT_VIEW (self),
+                                      _("Failed to load directory: %s"),
+                                      error->message);
+      return;
+    }
+
+  gtk_tree_view_set_model (self->tree_view, GTK_TREE_MODEL (model));
+
+  if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter))
+    {
+      GtkTreeSelection *selection;
+
+      selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->tree_view));
+      gtk_tree_selection_select_iter (selection, &iter);
+    }
+
+  gtk_widget_grab_focus (GTK_WIDGET (self->tree_view));
+}
+
+void
+gbp_ls_view_set_directory (GbpLsView *self,
+                           GFile     *directory)
+{
+  g_autofree gchar *title = NULL;
+  g_autofree gchar *name = NULL;
+  GFile *old_directory;
+
+  g_return_if_fail (GBP_IS_LS_VIEW (self));
+  g_return_if_fail (!directory || G_IS_FILE (directory));
+
+  if (directory == NULL)
+    {
+      IdeContext *context = ide_widget_get_context (GTK_WIDGET (self));
+      IdeVcs *vcs = ide_context_get_vcs (context);
+
+      directory = ide_vcs_get_working_directory (vcs);
+    }
+
+  g_assert (directory != NULL);
+
+  old_directory = gbp_ls_view_get_directory (self);
+
+  if (directory != NULL &&
+      old_directory != NULL &&
+      g_file_equal (directory, old_directory))
+    return;
+
+  g_clear_object (&self->model);
+
+  g_cancellable_cancel (self->model_cancellable);
+  g_clear_object (&self->model_cancellable);
+
+  self->model_cancellable = g_cancellable_new ();
+  self->model = gbp_ls_model_new (directory);
+
+  g_async_initable_init_async (G_ASYNC_INITABLE (self->model),
+                               G_PRIORITY_DEFAULT,
+                               self->model_cancellable,
+                               gbp_ls_view_init_model_cb,
+                               g_object_ref (self));
+
+  name = g_file_get_basename (directory);
+  title = g_strdup_printf (_("%s — Directory"), name);
+  ide_layout_view_set_title (IDE_LAYOUT_VIEW (self), title);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DIRECTORY]);
+}
diff --git a/src/plugins/ls/gbp-ls-view.h b/src/plugins/ls/gbp-ls-view.h
new file mode 100644
index 000000000..231004753
--- /dev/null
+++ b/src/plugins/ls/gbp-ls-view.h
@@ -0,0 +1,36 @@
+/* gbp-ls-view.h
+ *
+ * Copyright © 2018 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
+ */
+
+#pragma once
+
+#include <ide.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_LS_VIEW (gbp_ls_view_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpLsView, gbp_ls_view, GBP, LS_VIEW, IdeLayoutView)
+
+GtkWidget *gbp_ls_view_new           (void);
+GFile     *gbp_ls_view_get_directory (GbpLsView *self);
+void       gbp_ls_view_set_directory (GbpLsView *self,
+                                      GFile     *directory);
+
+G_END_DECLS
diff --git a/src/plugins/ls/gbp-ls-view.ui b/src/plugins/ls/gbp-ls-view.ui
new file mode 100644
index 000000000..cc5a797f4
--- /dev/null
+++ b/src/plugins/ls/gbp-ls-view.ui
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GbpLsView" parent="IdeLayoutView">
+    <child>
+      <object class="GtkScrolledWindow" id="scroller">
+        <property name="vexpand">true</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkTreeView" id="tree_view">
+            <property name="vexpand">true</property>
+            <property name="headers-visible">true</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkTreeViewColumn" id="name_column">
+                <property name="title" translatable="yes">Name</property>
+                <property name="expand">true</property>
+                <property name="visible">true</property>
+                <child>
+                  <object class="GtkCellRendererPixbuf" id="pixbuf_cell">
+                    <property name="xpad">8</property>
+                    <property name="ypad">6</property>
+                  </object>
+                  <attributes>
+                    <attribute name="gicon">0</attribute>
+                  </attributes>
+                  <cell-packing>
+                    <property name="expand">false</property>
+                  </cell-packing>
+                </child>
+                <child>
+                  <object class="GtkCellRendererText" id="name_cell">
+                    <property name="ellipsize">end</property>
+                  </object>
+                  <attributes>
+                    <attribute name="text">1</attribute>
+                  </attributes>
+                  <cell-packing>
+                    <property name="expand">true</property>
+                  </cell-packing>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkTreeViewColumn" id="size_column">
+                <property name="title" translatable="yes">Size</property>
+                <property name="visible">true</property>
+                <child>
+                  <object class="GtkCellRendererText" id="size_cell">
+                  </object>
+                  <cell-packing>
+                    <property name="expand">true</property>
+                  </cell-packing>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkTreeViewColumn" id="modified_column">
+                <property name="title" translatable="yes">Modified</property>
+                <property name="visible">true</property>
+                <child>
+                  <object class="GtkCellRendererText" id="modified_cell">
+                  </object>
+                  <cell-packing>
+                    <property name="expand">true</property>
+                  </cell-packing>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/plugins/ls/gbp-ls-workbench-addin.c b/src/plugins/ls/gbp-ls-workbench-addin.c
new file mode 100644
index 000000000..5786a683b
--- /dev/null
+++ b/src/plugins/ls/gbp-ls-workbench-addin.c
@@ -0,0 +1,179 @@
+/* gbp-ls-workbench-addin.c
+ *
+ * Copyright © 2018 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
+ */
+
+#include "config.h"
+
+#define G_LOG_DOMAIN "gbp-ls-workbench-addin"
+
+#include "gbp-ls-workbench-addin.h"
+#include "gbp-ls-view.h"
+
+struct _GbpLsWorkbenchAddin
+{
+  GObject       parent_instance;
+  IdeWorkbench *workbench;
+};
+
+typedef struct
+{
+  GFile     *file;
+  GbpLsView *view;
+} LocateView;
+
+static gchar *
+gbp_ls_workbench_addin_get_id (IdeWorkbenchAddin *addin)
+{
+  return g_strdup ("directory");
+}
+
+static gboolean
+gbp_ls_workbench_addin_can_open (IdeWorkbenchAddin *addin,
+                                 IdeUri            *uri,
+                                 const gchar       *content_type,
+                                 gint              *priority)
+{
+  g_assert (GBP_IS_LS_WORKBENCH_ADDIN (addin));
+  g_assert (priority != NULL);
+
+  if (g_strcmp0 (content_type, "inode/directory") == 0)
+    {
+      *priority = -100;
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+locate_view (GtkWidget *view,
+             gpointer   user_data)
+{
+  LocateView *locate = user_data;
+  GFile *file;
+
+  g_assert (IDE_IS_LAYOUT_VIEW (view));
+  g_assert (locate != NULL);
+
+  if (locate->view != NULL)
+    return;
+
+  if (!GBP_IS_LS_VIEW (view))
+    return;
+
+  file = gbp_ls_view_get_directory (GBP_LS_VIEW (view));
+  if (g_file_equal (file, locate->file))
+    locate->view = GBP_LS_VIEW (view);
+}
+
+static void
+gbp_ls_workbench_addin_open_async (IdeWorkbenchAddin     *addin,
+                                   IdeUri                *uri,
+                                   const gchar           *content_type,
+                                   IdeWorkbenchOpenFlags  flags,
+                                   GCancellable          *cancellable,
+                                   GAsyncReadyCallback    callback,
+                                   gpointer               user_data)
+{
+  GbpLsWorkbenchAddin *self = (GbpLsWorkbenchAddin *)addin;
+  g_autoptr(IdeTask) task = NULL;
+  g_autoptr(GFile) file = NULL;
+  IdePerspective *editor;
+  GbpLsView *view;
+  LocateView locate = { 0 };
+
+  g_assert (GBP_IS_LS_WORKBENCH_ADDIN (self));
+  g_assert (IDE_IS_WORKBENCH (self->workbench));
+  g_assert (uri != NULL);
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, gbp_ls_workbench_addin_open_async);
+
+  editor = ide_workbench_get_perspective_by_name (self->workbench, "editor");
+  file = ide_uri_to_file (uri);
+
+  /* First try to find an existing view for the file */
+  locate.file = file;
+  ide_workbench_views_foreach (self->workbench, locate_view, &locate);
+  if (locate.view != NULL)
+    {
+      ide_workbench_focus (self->workbench, GTK_WIDGET (locate.view));
+      ide_task_return_boolean (task, TRUE);
+      return;
+    }
+
+  view = g_object_new (GBP_TYPE_LS_VIEW,
+                       "close-on-activate", TRUE,
+                       "directory", file,
+                       "visible", TRUE,
+                       NULL);
+  gtk_container_add (GTK_CONTAINER (editor), GTK_WIDGET (view));
+
+  ide_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+gbp_ls_workbench_addin_open_finish (IdeWorkbenchAddin  *addin,
+                                    GAsyncResult       *result,
+                                    GError            **error)
+{
+  g_assert (GBP_IS_LS_WORKBENCH_ADDIN (addin));
+  g_assert (IDE_IS_TASK (result));
+
+  return ide_task_propagate_boolean (IDE_TASK (result), error);
+}
+
+static void
+gbp_ls_workbench_addin_load (IdeWorkbenchAddin *addin,
+                             IdeWorkbench      *workbench)
+{
+  GBP_LS_WORKBENCH_ADDIN (addin)->workbench = workbench;
+}
+
+static void
+gbp_ls_workbench_addin_unload (IdeWorkbenchAddin *addin,
+                               IdeWorkbench      *workbench)
+{
+  GBP_LS_WORKBENCH_ADDIN (addin)->workbench = NULL;
+}
+
+static void
+workbench_addin_iface_init (IdeWorkbenchAddinInterface *iface)
+{
+  iface->get_id = gbp_ls_workbench_addin_get_id;
+  iface->can_open = gbp_ls_workbench_addin_can_open;
+  iface->open_async = gbp_ls_workbench_addin_open_async;
+  iface->open_finish = gbp_ls_workbench_addin_open_finish;
+  iface->load = gbp_ls_workbench_addin_load;
+  iface->unload = gbp_ls_workbench_addin_unload;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GbpLsWorkbenchAddin, gbp_ls_workbench_addin, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (IDE_TYPE_WORKBENCH_ADDIN, workbench_addin_iface_init))
+
+static void
+gbp_ls_workbench_addin_class_init (GbpLsWorkbenchAddinClass *klass)
+{
+}
+
+static void
+gbp_ls_workbench_addin_init (GbpLsWorkbenchAddin *self)
+{
+}
diff --git a/src/plugins/ls/gbp-ls-workbench-addin.h b/src/plugins/ls/gbp-ls-workbench-addin.h
new file mode 100644
index 000000000..b494e30e5
--- /dev/null
+++ b/src/plugins/ls/gbp-ls-workbench-addin.h
@@ -0,0 +1,31 @@
+/* gbp-ls-workbench-addin.h
+ *
+ * Copyright © 2018 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
+ */
+
+#pragma once
+
+#include <ide.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_LS_WORKBENCH_ADDIN (gbp_ls_workbench_addin_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpLsWorkbenchAddin, gbp_ls_workbench_addin, GBP, LS_WORKBENCH_ADDIN, GObject)
+
+G_END_DECLS
diff --git a/src/plugins/ls/ls.gresource.xml b/src/plugins/ls/ls.gresource.xml
new file mode 100644
index 000000000..26e2dec94
--- /dev/null
+++ b/src/plugins/ls/ls.gresource.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/org/gnome/builder/plugins">
+    <file>ls.plugin</file>
+  </gresource>
+  <gresource prefix="/org/gnome/builder/plugins/ls">
+    <file preprocess="xml-stripblanks">gbp-ls-view.ui</file>
+  </gresource>
+</gresources>
diff --git a/src/plugins/ls/ls.plugin b/src/plugins/ls/ls.plugin
new file mode 100644
index 000000000..18af04853
--- /dev/null
+++ b/src/plugins/ls/ls.plugin
@@ -0,0 +1,9 @@
+[Plugin]
+Module=ls
+Name=View Directory Listings
+Description=List files in a directory as a view
+Authors=Christian Hergert <christian hergert me>
+Copyright=Copyright © 2018 Christian Hergert
+Builtin=true
+Depends=editor;
+Embedded=gbp_ls_register_types
diff --git a/src/plugins/ls/meson.build b/src/plugins/ls/meson.build
new file mode 100644
index 000000000..1dcea074e
--- /dev/null
+++ b/src/plugins/ls/meson.build
@@ -0,0 +1,19 @@
+if get_option('with_ls')
+
+grep_resources = gnome.compile_resources(
+  'ls-resources',
+  'ls.gresource.xml',
+  c_name: 'gbp_ls',
+)
+
+grep_sources = [
+  'gbp-ls-model.c',
+  'gbp-ls-plugin.c',
+  'gbp-ls-view.c',
+  'gbp-ls-workbench-addin.c',
+]
+
+gnome_builder_plugins_sources += files(grep_sources)
+gnome_builder_plugins_sources += grep_resources[0]
+
+endif
diff --git a/src/plugins/meson.build b/src/plugins/meson.build
index 76603122e..14e7061a1 100644
--- a/src/plugins/meson.build
+++ b/src/plugins/meson.build
@@ -43,6 +43,7 @@ subdir('html-completion')
 subdir('html-preview')
 subdir('jedi')
 subdir('jhbuild')
+subdir('ls')
 subdir('make')
 subdir('maven')
 subdir('meson')
@@ -129,6 +130,7 @@ status += [
   'HTML Preview .......... : @0@'.format(get_option('with_html_preview')),
   'Python Jedi ........... : @0@'.format(get_option('with_jedi')),
   'JHBuild ............... : @0@'.format(get_option('with_jhbuild')),
+  'Directory View ........ : @0@'.format(get_option('with_ls')),
   'Make .................. : @0@'.format(get_option('with_make')),
   'Maven.................. : @0@'.format(get_option('with_maven')),
   'Meson ................. : @0@'.format(get_option('with_meson')),



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