[gnome-builder] plugins/find-other-file: port to C



commit c8c38d64a43a02df432f472a5f65a72e90dfe13a
Author: Christian Hergert <chergert redhat com>
Date:   Mon Jul 11 22:41:48 2022 -0700

    plugins/find-other-file: port to C
    
    This both ports the plugin to C and improves how we integrate the feature
    into the application so that the command-bar/global-search is not needed
    to be used. Instead a button is placed in the statusbar.

 .../find-other-file/find-other-file-plugin.c       |  42 ++++
 .../find-other-file/find-other-file.gresource.xml  |   8 +
 src/plugins/find-other-file/find-other-file.plugin |  11 +-
 src/plugins/find-other-file/find_other_file.py     | 169 -------------
 .../find-other-file/gbp-find-other-file-browser.c  | 234 ++++++++++++++++++
 .../find-other-file/gbp-find-other-file-browser.h  |  37 +++
 .../find-other-file/gbp-find-other-file-popover.c  | 195 +++++++++++++++
 .../find-other-file/gbp-find-other-file-popover.h  |  34 +++
 .../find-other-file/gbp-find-other-file-popover.ui |  80 ++++++
 .../gbp-find-other-file-workspace-addin.c          | 270 +++++++++++++++++++++
 .../gbp-find-other-file-workspace-addin.h          |  31 +++
 src/plugins/find-other-file/gbp-found-file.c       | 244 +++++++++++++++++++
 src/plugins/find-other-file/gbp-found-file.h       |  36 +++
 .../gbp-simple-similar-file-locator.c              | 171 +++++++++++++
 .../gbp-simple-similar-file-locator.h              |  31 +++
 src/plugins/find-other-file/gtk/keybindings.json   |   1 +
 src/plugins/find-other-file/meson.build            |  21 +-
 17 files changed, 1432 insertions(+), 183 deletions(-)
---
diff --git a/src/plugins/find-other-file/find-other-file-plugin.c 
b/src/plugins/find-other-file/find-other-file-plugin.c
new file mode 100644
index 000000000..a0c02bf1d
--- /dev/null
+++ b/src/plugins/find-other-file/find-other-file-plugin.c
@@ -0,0 +1,42 @@
+/* find-other-file-plugin.c
+ *
+ * Copyright 2022 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 "find-other-file-plugin"
+
+#include "config.h"
+
+#include <libpeas/peas.h>
+
+#include <libide-gui.h>
+#include <libide-projects.h>
+
+#include "gbp-simple-similar-file-locator.h"
+#include "gbp-find-other-file-workspace-addin.h"
+
+_IDE_EXTERN void
+_gbp_find_other_file_register_types (PeasObjectModule *module)
+{
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_SIMILAR_FILE_LOCATOR,
+                                              GBP_TYPE_SIMPLE_SIMILAR_FILE_LOCATOR);
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_WORKSPACE_ADDIN,
+                                              GBP_TYPE_FIND_OTHER_FILE_WORKSPACE_ADDIN);
+}
diff --git a/src/plugins/find-other-file/find-other-file.gresource.xml 
b/src/plugins/find-other-file/find-other-file.gresource.xml
new file mode 100644
index 000000000..e0108f7c9
--- /dev/null
+++ b/src/plugins/find-other-file/find-other-file.gresource.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/plugins/find-other-file">
+    <file>find-other-file.plugin</file>
+    <file>gtk/keybindings.json</file>
+    <file preprocess="xml-stripblanks">gbp-find-other-file-popover.ui</file>
+  </gresource>
+</gresources>
diff --git a/src/plugins/find-other-file/find-other-file.plugin 
b/src/plugins/find-other-file/find-other-file.plugin
index bb9e3c7ad..366bb39b4 100644
--- a/src/plugins/find-other-file/find-other-file.plugin
+++ b/src/plugins/find-other-file/find-other-file.plugin
@@ -1,12 +1,9 @@
 [Plugin]
 Authors=Christian Hergert <christian hergert me>
 Builtin=true
-Copyright=Copyright © 2017 Christian Hergert
-Depends=editor;
+Copyright=Copyright © 2017-2022 Christian Hergert
 Description=Allows the user to rotate through other files similarly named to the open document.
-Hidden=true
-Loader=python3
-Module=find_other_file
-Name=Find other files
-X-Builder-ABI=@PACKAGE_ABI@
+Embedded=_gbp_find_other_file_register_types
+Module=find-other-file
+Name=Find Similar Files
 X-Workspace-Kind=primary;editor;
diff --git a/src/plugins/find-other-file/gbp-find-other-file-browser.c 
b/src/plugins/find-other-file/gbp-find-other-file-browser.c
new file mode 100644
index 000000000..21cee9d69
--- /dev/null
+++ b/src/plugins/find-other-file/gbp-find-other-file-browser.c
@@ -0,0 +1,234 @@
+/* gbp-find-other-file-browser.c
+ *
+ * Copyright 2022 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-find-other-file-browser"
+
+#include "config.h"
+
+#include "gbp-find-other-file-browser.h"
+#include "gbp-found-file.h"
+
+struct _GbpFindOtherFileBrowser
+{
+  GObject    parent_instance;
+  GPtrArray *items;
+  GFile     *file;
+  GFile     *root;
+};
+
+enum {
+  PROP_0,
+  PROP_ROOT,
+  PROP_FILE,
+  N_PROPS
+};
+
+static GType
+gbp_find_other_file_browser_get_item_type (GListModel *model)
+{
+  return G_TYPE_FILE;
+}
+
+static guint
+gbp_find_other_file_browser_get_n_items (GListModel *model)
+{
+  return GBP_FIND_OTHER_FILE_BROWSER (model)->items->len;
+}
+
+static gpointer
+gbp_find_other_file_browser_get_item (GListModel *model,
+                                      guint       position)
+{
+  GbpFindOtherFileBrowser *self = GBP_FIND_OTHER_FILE_BROWSER (model);
+
+  if (position < self->items->len)
+    return g_object_ref (g_ptr_array_index (self->items, position));
+
+  return NULL;
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface)
+{
+  iface->get_item_type = gbp_find_other_file_browser_get_item_type;
+  iface->get_n_items = gbp_find_other_file_browser_get_n_items;
+  iface->get_item = gbp_find_other_file_browser_get_item;
+}
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (GbpFindOtherFileBrowser, gbp_find_other_file_browser, G_TYPE_OBJECT,
+                               G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+gbp_find_other_file_browser_reload (GbpFindOtherFileBrowser *self)
+{
+  guint old_len = 0;
+  guint new_len = 0;
+
+  g_assert (GBP_IS_FIND_OTHER_FILE_BROWSER (self));
+
+  old_len = self->items->len;
+
+  if (old_len)
+    g_ptr_array_remove_range (self->items, 0, old_len);
+
+  if (self->root != NULL &&
+      self->file != NULL &&
+      g_file_has_prefix (self->file, self->root))
+    {
+      GFile *parent = g_file_get_parent (self->file);
+
+      while (parent != NULL &&
+             (g_file_has_prefix (parent, self->root) ||
+              g_file_equal (parent, self->root)))
+        {
+          g_ptr_array_insert (self->items, 0, parent);
+          parent = g_file_get_parent (parent);
+        }
+
+      g_clear_object (&parent);
+    }
+
+  new_len = self->items->len;
+
+  if (old_len || new_len)
+    g_list_model_items_changed (G_LIST_MODEL (self), 0, old_len, new_len);
+}
+
+static void
+gbp_find_other_file_browser_dispose (GObject *object)
+{
+  GbpFindOtherFileBrowser *self = (GbpFindOtherFileBrowser *)object;
+
+  g_clear_object (&self->file);
+  g_clear_object (&self->root);
+  g_clear_pointer (&self->items, g_ptr_array_unref);
+
+  G_OBJECT_CLASS (gbp_find_other_file_browser_parent_class)->dispose (object);
+}
+
+static void
+gbp_find_other_file_browser_get_property (GObject    *object,
+                                          guint       prop_id,
+                                          GValue     *value,
+                                          GParamSpec *pspec)
+{
+  GbpFindOtherFileBrowser *self = GBP_FIND_OTHER_FILE_BROWSER (object);
+
+  switch (prop_id)
+    {
+    case PROP_FILE:
+      g_value_set_object (value, self->file);
+      break;
+
+    case PROP_ROOT:
+      g_value_set_object (value, self->root);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_find_other_file_browser_set_property (GObject      *object,
+                                          guint         prop_id,
+                                          const GValue *value,
+                                          GParamSpec   *pspec)
+{
+  GbpFindOtherFileBrowser *self = GBP_FIND_OTHER_FILE_BROWSER (object);
+
+  switch (prop_id)
+    {
+    case PROP_FILE:
+      gbp_find_other_file_browser_set_file (self, g_value_get_object (value));
+      break;
+
+    case PROP_ROOT:
+      gbp_find_other_file_browser_set_root (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_find_other_file_browser_class_init (GbpFindOtherFileBrowserClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = gbp_find_other_file_browser_dispose;
+  object_class->get_property = gbp_find_other_file_browser_get_property;
+  object_class->set_property = gbp_find_other_file_browser_set_property;
+
+  properties [PROP_FILE] =
+    g_param_spec_object ("file", NULL, NULL,
+                         G_TYPE_FILE,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_ROOT] =
+    g_param_spec_object ("root", NULL, NULL,
+                         G_TYPE_FILE,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gbp_find_other_file_browser_init (GbpFindOtherFileBrowser *self)
+{
+  self->items = g_ptr_array_new_with_free_func (g_object_unref);
+}
+
+void
+gbp_find_other_file_browser_set_file (GbpFindOtherFileBrowser *self,
+                                      GFile                   *file)
+{
+  g_return_if_fail (GBP_IS_FIND_OTHER_FILE_BROWSER (self));
+  g_return_if_fail (!file || G_IS_FILE (file));
+
+  if (g_set_object (&self->file, file))
+    {
+      gbp_find_other_file_browser_reload (self);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FILE]);
+    }
+}
+
+void
+gbp_find_other_file_browser_set_root (GbpFindOtherFileBrowser *self,
+                                      GFile                   *root)
+{
+  g_return_if_fail (GBP_IS_FIND_OTHER_FILE_BROWSER (self));
+  g_return_if_fail (!root || G_IS_FILE (root));
+
+  if (g_set_object (&self->root, root))
+    {
+      gbp_find_other_file_browser_reload (self);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ROOT]);
+    }
+}
+
+GbpFindOtherFileBrowser *
+gbp_find_other_file_browser_new (void)
+{
+  return g_object_new (GBP_TYPE_FIND_OTHER_FILE_BROWSER, NULL);
+}
diff --git a/src/plugins/find-other-file/gbp-find-other-file-browser.h 
b/src/plugins/find-other-file/gbp-find-other-file-browser.h
new file mode 100644
index 000000000..f93adb1cd
--- /dev/null
+++ b/src/plugins/find-other-file/gbp-find-other-file-browser.h
@@ -0,0 +1,37 @@
+/* gbp-find-other-file-browser.h
+ *
+ * Copyright 2022 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_FIND_OTHER_FILE_BROWSER (gbp_find_other_file_browser_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpFindOtherFileBrowser, gbp_find_other_file_browser, GBP, FIND_OTHER_FILE_BROWSER, 
GObject)
+
+GbpFindOtherFileBrowser *gbp_find_other_file_browser_new      (void);
+void                     gbp_find_other_file_browser_set_root (GbpFindOtherFileBrowser *self,
+                                                               GFile                   *root);
+void                     gbp_find_other_file_browser_set_file (GbpFindOtherFileBrowser *self,
+                                                               GFile                   *file);
+
+G_END_DECLS
diff --git a/src/plugins/find-other-file/gbp-find-other-file-popover.c 
b/src/plugins/find-other-file/gbp-find-other-file-popover.c
new file mode 100644
index 000000000..33b4e57c4
--- /dev/null
+++ b/src/plugins/find-other-file/gbp-find-other-file-popover.c
@@ -0,0 +1,195 @@
+/* gbp-find-other-file-popover.c
+ *
+ * Copyright 2022 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-find-other-file-popover"
+
+#include "config.h"
+
+#include <libide-gui.h>
+#include <libide-projects.h>
+
+#include "gbp-find-other-file-popover.h"
+#include "gbp-found-file.h"
+
+struct _GbpFindOtherFilePopover
+{
+  GtkPopover   parent_instance;
+  GListModel  *model;
+  GtkListView *list_view;
+};
+
+G_DEFINE_FINAL_TYPE (GbpFindOtherFilePopover, gbp_find_other_file_popover, GTK_TYPE_POPOVER)
+
+enum {
+  PROP_0,
+  PROP_MODEL,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+gbp_find_other_file_popover_activate_cb (GbpFindOtherFilePopover *self,
+                                         guint                    position,
+                                         GtkListView             *list_view)
+{
+  IdeWorkspace *workspace;
+  GbpFoundFile *file;
+  GListModel *model;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_FIND_OTHER_FILE_POPOVER (self));
+  g_assert (GTK_IS_LIST_VIEW (list_view));
+
+  g_debug ("Activating file row at position %u", position);
+
+  model = G_LIST_MODEL (gtk_list_view_get_model (list_view));
+  file = g_list_model_get_item (model, position);
+  workspace = ide_widget_get_workspace (GTK_WIDGET (self));
+
+  gtk_popover_popdown (GTK_POPOVER (self));
+  gbp_found_file_open (file, workspace);
+
+  IDE_EXIT;
+}
+
+static gpointer
+file_to_found_file (gpointer item,
+                    gpointer user_data)
+{
+  GFile *workdir = user_data;
+  g_autoptr(GFile) file = item;
+
+  g_assert (G_IS_FILE (workdir));
+  g_assert (G_IS_FILE (file));
+
+  return gbp_found_file_new (workdir, file);
+}
+
+void
+gbp_find_other_file_popover_set_model (GbpFindOtherFilePopover *self,
+                                       GListModel              *model)
+{
+  g_assert (GBP_IS_FIND_OTHER_FILE_POPOVER (self));
+  g_assert (!model || G_IS_LIST_MODEL (model));
+
+  if (g_set_object (&self->model, model))
+    {
+      g_autoptr(GtkNoSelection) selection = NULL;
+
+      if (model != NULL)
+        {
+          IdeContext *context = ide_widget_get_context (GTK_WIDGET (self));
+          GtkMapListModel *map;
+
+          map = gtk_map_list_model_new (g_object_ref (model),
+                                        file_to_found_file,
+                                        ide_context_ref_workdir (context),
+                                        g_object_unref);
+          selection = gtk_no_selection_new (G_LIST_MODEL (map));
+        }
+      else
+        {
+          selection = gtk_no_selection_new (G_LIST_MODEL (g_list_store_new (GBP_TYPE_FOUND_FILE)));
+        }
+
+      gtk_list_view_set_model (self->list_view, GTK_SELECTION_MODEL (selection));
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MODEL]);
+    }
+}
+
+static void
+gbp_find_other_file_popover_dispose (GObject *object)
+{
+  GbpFindOtherFilePopover *self = (GbpFindOtherFilePopover *)object;
+
+  g_clear_object (&self->model);
+
+  G_OBJECT_CLASS (gbp_find_other_file_popover_parent_class)->dispose (object);
+}
+
+static void
+gbp_find_other_file_popover_get_property (GObject    *object,
+                                          guint       prop_id,
+                                          GValue     *value,
+                                          GParamSpec *pspec)
+{
+  GbpFindOtherFilePopover *self = GBP_FIND_OTHER_FILE_POPOVER (object);
+
+  switch (prop_id)
+    {
+    case PROP_MODEL:
+      g_value_set_object (value, self->model);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_find_other_file_popover_set_property (GObject      *object,
+                                          guint         prop_id,
+                                          const GValue *value,
+                                          GParamSpec   *pspec)
+{
+  GbpFindOtherFilePopover *self = GBP_FIND_OTHER_FILE_POPOVER (object);
+
+  switch (prop_id)
+    {
+    case PROP_MODEL:
+      gbp_find_other_file_popover_set_model (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_find_other_file_popover_class_init (GbpFindOtherFilePopoverClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->dispose = gbp_find_other_file_popover_dispose;
+  object_class->get_property = gbp_find_other_file_popover_get_property;
+  object_class->set_property = gbp_find_other_file_popover_set_property;
+
+  properties [PROP_MODEL] =
+    g_param_spec_object ("model", NULL, NULL,
+                         G_TYPE_LIST_MODEL,
+                         (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, 
"/plugins/find-other-file/gbp-find-other-file-popover.ui");
+  gtk_widget_class_bind_template_child (widget_class, GbpFindOtherFilePopover, list_view);
+  gtk_widget_class_bind_template_callback (widget_class, gbp_find_other_file_popover_activate_cb);
+
+  g_type_ensure (GBP_TYPE_FOUND_FILE);
+}
+
+static void
+gbp_find_other_file_popover_init (GbpFindOtherFilePopover *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
diff --git a/src/plugins/find-other-file/gbp-find-other-file-popover.h 
b/src/plugins/find-other-file/gbp-find-other-file-popover.h
new file mode 100644
index 000000000..9ec8a6c9c
--- /dev/null
+++ b/src/plugins/find-other-file/gbp-find-other-file-popover.h
@@ -0,0 +1,34 @@
+/* gbp-find-other-file-popover.h
+ *
+ * Copyright 2022 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 <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_FIND_OTHER_FILE_POPOVER (gbp_find_other_file_popover_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpFindOtherFilePopover, gbp_find_other_file_popover, GBP, FIND_OTHER_FILE_POPOVER, 
GtkPopover)
+
+void gbp_find_other_file_popover_set_model (GbpFindOtherFilePopover *self,
+                                            GListModel              *model);
+
+G_END_DECLS
diff --git a/src/plugins/find-other-file/gbp-find-other-file-popover.ui 
b/src/plugins/find-other-file/gbp-find-other-file-popover.ui
new file mode 100644
index 000000000..96aa995cc
--- /dev/null
+++ b/src/plugins/find-other-file/gbp-find-other-file-popover.ui
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GbpFindOtherFilePopover" parent="GtkPopover">
+    <style>
+      <class name="menu"/>
+    </style>
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">vertical</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkScrolledWindow" id="scroller">
+            <property name="propagate-natural-height">true</property>
+            <property name="propagate-natural-width">true</property>
+            <property name="min-content-height">100</property>
+            <property name="max-content-height">600</property>
+            <property name="min-content-width">300</property>
+            <property name="max-content-width">300</property>
+            <child>
+              <object class="GtkListView" id="list_view">
+                <signal name="activate" handler="gbp_find_other_file_popover_activate_cb" swapped="true" 
object="GbpFindOtherFilePopover"/>
+                <property name="orientation">vertical</property>
+                <property name="single-click-activate">True</property>
+                <property name="factory">
+                  <object class="GtkBuilderListItemFactory">
+                    <property name="bytes"><![CDATA[
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GtkListItem">
+    <property name="child">
+      <object class="GtkBox">
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkImage">
+            <property name="pixel-size">16</property>
+            <binding name="gicon">
+              <lookup name="gicon" type="GbpFoundFile">
+                <lookup name="item">GtkListItem</lookup>
+              </lookup>
+            </binding>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel">
+            <property name="halign">start</property>
+            <property name="hexpand">true</property>
+            <property name="ellipsize">start</property>
+            <binding name="label">
+              <lookup name="display-name" type="GbpFoundFile">
+                <lookup name="item">GtkListItem</lookup>
+              </lookup>
+            </binding>
+          </object>
+        </child>
+        <child>
+          <object class="GtkImage">
+            <property name="icon-name">go-next-symbolic</property>
+            <binding name="visible">
+              <lookup name="is-directory" type="GbpFoundFile">
+                <lookup name="item">GtkListItem</lookup>
+              </lookup>
+            </binding>
+          </object>
+        </child>
+      </object>
+    </property>
+  </template>
+</interface>
+]]>
+                    </property>
+                  </object>
+                </property>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/plugins/find-other-file/gbp-find-other-file-workspace-addin.c 
b/src/plugins/find-other-file/gbp-find-other-file-workspace-addin.c
new file mode 100644
index 000000000..8d0c4d933
--- /dev/null
+++ b/src/plugins/find-other-file/gbp-find-other-file-workspace-addin.c
@@ -0,0 +1,270 @@
+/* gbp-find-other-file-workspace-addin.c
+ *
+ * Copyright 2022 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-find-other-file-workspace-addin"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include <libide-gui.h>
+#include <libide-editor.h>
+#include <libide-projects.h>
+
+#include "gbp-find-other-file-browser.h"
+#include "gbp-find-other-file-popover.h"
+#include "gbp-find-other-file-workspace-addin.h"
+
+struct _GbpFindOtherFileWorkspaceAddin
+{
+  GObject                  parent_instance;
+  IdeWorkspace            *workspace;
+  GtkMenuButton           *menu_button;
+  GtkLabel                *label;
+  GtkImage                *image;
+  GbpFindOtherFileBrowser *browser;
+  GbpFindOtherFilePopover *popover;
+};
+
+static void
+find_other_file_action (GSimpleAction *action,
+                        GVariant      *param,
+                        gpointer       user_data)
+{
+  GbpFindOtherFileWorkspaceAddin *self = user_data;
+
+  g_assert (GBP_IS_FIND_OTHER_FILE_WORKSPACE_ADDIN (self));
+
+  if (self->menu_button != NULL &&
+      gtk_widget_get_visible (GTK_WIDGET (self->menu_button)))
+    gtk_menu_button_popup (self->menu_button);
+}
+
+static GActionEntry actions[] = {
+  { "find-other-file", find_other_file_action },
+};
+
+static void
+gbp_find_other_file_workspace_addin_clear (GbpFindOtherFileWorkspaceAddin *self)
+{
+  g_assert (GBP_IS_FIND_OTHER_FILE_WORKSPACE_ADDIN (self));
+
+  gtk_widget_hide (GTK_WIDGET (self->menu_button));
+  gbp_find_other_file_popover_set_model (self->popover, NULL);
+  gbp_find_other_file_browser_set_file (self->browser, NULL);
+}
+
+static GListModel *
+join_models (GListModel *a,
+             GListModel *b)
+{
+  GListStore *joined = g_list_store_new (G_TYPE_LIST_MODEL);
+
+  g_assert (G_IS_LIST_MODEL (a));
+  g_assert (G_IS_LIST_MODEL (b));
+
+  g_list_store_append (joined, a);
+  g_list_store_append (joined, b);
+
+  return G_LIST_MODEL (gtk_flatten_list_model_new (G_LIST_MODEL (joined)));
+}
+
+static void
+gbp_find_other_file_workspace_addin_list_similar_cb (GObject      *object,
+                                                     GAsyncResult *result,
+                                                     gpointer      user_data)
+{
+  IdeProject *project = (IdeProject *)object;
+  g_autoptr(GbpFindOtherFileWorkspaceAddin) self = user_data;
+  g_autoptr(GListModel) model = NULL;
+  g_autoptr(GListModel) joined = NULL;
+  g_autoptr(GError) error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_PROJECT (project));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (GBP_IS_FIND_OTHER_FILE_WORKSPACE_ADDIN (self));
+
+  /* Maybe we were disposed already */
+  if (self->workspace == NULL)
+    IDE_EXIT;
+
+  if (!(model = ide_project_list_similar_finish (project, result, &error)))
+    {
+      if (!ide_error_ignore (error))
+        g_warning ("%s", error->message);
+      gbp_find_other_file_workspace_addin_clear (self);
+      IDE_EXIT;
+    }
+
+  g_assert (GBP_IS_FIND_OTHER_FILE_BROWSER (self->browser));
+  g_assert (G_IS_LIST_MODEL (model));
+
+  joined = join_models (G_LIST_MODEL (self->browser), model);
+  gbp_find_other_file_popover_set_model (self->popover, joined);
+  gtk_widget_show (GTK_WIDGET (self->menu_button));
+
+  IDE_EXIT;
+}
+
+static void
+gbp_find_other_file_workspace_addin_page_changed (IdeWorkspaceAddin *addin,
+                                                  IdePage           *page)
+{
+  GbpFindOtherFileWorkspaceAddin *self = (GbpFindOtherFileWorkspaceAddin *)addin;
+  IdeProject *project;
+  IdeContext *context;
+  GFile *file;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_FIND_OTHER_FILE_WORKSPACE_ADDIN (self));
+  g_assert (!page || IDE_IS_PAGE (page));
+
+  gbp_find_other_file_workspace_addin_clear (self);
+
+  if (!IDE_IS_EDITOR_PAGE (page))
+    IDE_EXIT;
+
+  context = ide_workspace_get_context (self->workspace);
+  project = ide_project_from_context (context);
+  file = ide_editor_page_get_file (IDE_EDITOR_PAGE (page));
+
+  gbp_find_other_file_browser_set_file (self->browser, file);
+
+  ide_project_list_similar_async (project,
+                                  file,
+                                  NULL,
+                                  gbp_find_other_file_workspace_addin_list_similar_cb,
+                                  g_object_ref (self));
+
+  IDE_EXIT;
+}
+
+static void
+gbp_find_other_file_workspace_addin_load (IdeWorkspaceAddin *addin,
+                                          IdeWorkspace      *workspace)
+{
+  GbpFindOtherFileWorkspaceAddin *self = (GbpFindOtherFileWorkspaceAddin *)addin;
+  g_autoptr(GFile) workdir = NULL;
+  PanelStatusbar *statusbar;
+  IdeContext *context;
+  GtkBox *box;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_FIND_OTHER_FILE_WORKSPACE_ADDIN (self));
+  g_assert (IDE_IS_WORKSPACE (workspace));
+
+  self->workspace = workspace;
+
+  context = ide_workspace_get_context (workspace);
+  workdir = ide_context_ref_workdir (context);
+
+  self->browser = gbp_find_other_file_browser_new ();
+  gbp_find_other_file_browser_set_root (self->browser, workdir);
+
+  self->popover = g_object_new (GBP_TYPE_FIND_OTHER_FILE_POPOVER,
+                                NULL);
+  box = g_object_new (GTK_TYPE_BOX,
+                      "orientation", GTK_ORIENTATION_HORIZONTAL,
+                      "spacing", 6,
+                      NULL);
+  self->image = g_object_new (GTK_TYPE_IMAGE,
+                              "icon-name", "folder-symbolic",
+                              "pixel-size", 16,
+                              NULL);
+  gtk_box_append (box, GTK_WIDGET (self->image));
+#if 0
+  self->label = g_object_new (GTK_TYPE_LABEL,
+                              "ellipsize", PANGO_ELLIPSIZE_END,
+                              NULL);
+  gtk_box_append (box, GTK_WIDGET (self->label));
+#endif
+  self->menu_button = g_object_new (GTK_TYPE_MENU_BUTTON,
+                                    "focus-on-click", FALSE,
+                                    "popover", self->popover,
+                                    "direction", GTK_ARROW_UP,
+                                    "child", box,
+                                    "visible", FALSE,
+                                    "tooltip-text", _("Similar Files"),
+                                    NULL);
+
+  statusbar = ide_workspace_get_statusbar (workspace);
+  panel_statusbar_add_suffix (statusbar, 10000, GTK_WIDGET (self->menu_button));
+
+  g_action_map_add_action_entries (G_ACTION_MAP (workspace),
+                                   actions,
+                                   G_N_ELEMENTS (actions),
+                                   self);
+
+  IDE_EXIT;
+}
+
+static void
+gbp_find_other_file_workspace_addin_unload (IdeWorkspaceAddin *addin,
+                                            IdeWorkspace      *workspace)
+{
+  GbpFindOtherFileWorkspaceAddin *self = (GbpFindOtherFileWorkspaceAddin *)addin;
+  PanelStatusbar *statusbar;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_FIND_OTHER_FILE_WORKSPACE_ADDIN (self));
+  g_assert (IDE_IS_WORKSPACE (workspace));
+
+  g_clear_object (&self->browser);
+
+  for (guint i = 0; i < G_N_ELEMENTS (actions); i++)
+    g_action_map_remove_action (G_ACTION_MAP (workspace), actions[i].name);
+
+  statusbar = ide_workspace_get_statusbar (workspace);
+  panel_statusbar_remove (statusbar, GTK_WIDGET (self->menu_button));
+  self->menu_button = NULL;
+  self->popover = NULL;
+  self->label = NULL;
+  self->image = NULL;
+
+  self->workspace = NULL;
+
+  IDE_EXIT;
+}
+
+static void
+workspace_addin_iface_init (IdeWorkspaceAddinInterface *iface)
+{
+  iface->load = gbp_find_other_file_workspace_addin_load;
+  iface->unload = gbp_find_other_file_workspace_addin_unload;
+  iface->page_changed = gbp_find_other_file_workspace_addin_page_changed;
+}
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (GbpFindOtherFileWorkspaceAddin, gbp_find_other_file_workspace_addin, 
G_TYPE_OBJECT,
+                               G_IMPLEMENT_INTERFACE (IDE_TYPE_WORKSPACE_ADDIN, workspace_addin_iface_init))
+
+static void
+gbp_find_other_file_workspace_addin_class_init (GbpFindOtherFileWorkspaceAddinClass *klass)
+{
+}
+
+static void
+gbp_find_other_file_workspace_addin_init (GbpFindOtherFileWorkspaceAddin *self)
+{
+}
diff --git a/src/plugins/find-other-file/gbp-find-other-file-workspace-addin.h 
b/src/plugins/find-other-file/gbp-find-other-file-workspace-addin.h
new file mode 100644
index 000000000..a069b6f60
--- /dev/null
+++ b/src/plugins/find-other-file/gbp-find-other-file-workspace-addin.h
@@ -0,0 +1,31 @@
+/* gbp-find-other-file-workspace-addin.h
+ *
+ * Copyright 2022 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 <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_FIND_OTHER_FILE_WORKSPACE_ADDIN (gbp_find_other_file_workspace_addin_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpFindOtherFileWorkspaceAddin, gbp_find_other_file_workspace_addin, GBP, 
FIND_OTHER_FILE_WORKSPACE_ADDIN, GObject)
+
+G_END_DECLS
diff --git a/src/plugins/find-other-file/gbp-found-file.c b/src/plugins/find-other-file/gbp-found-file.c
new file mode 100644
index 000000000..d3ae24da8
--- /dev/null
+++ b/src/plugins/find-other-file/gbp-found-file.c
@@ -0,0 +1,244 @@
+/* gbp-found-file.c
+ *
+ * Copyright 2022 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-found-file"
+
+#include "config.h"
+
+#include "gbp-found-file.h"
+
+struct _GbpFoundFile
+{
+  GObject parent_instance;
+  GFile *file;
+  GFileInfo *info;
+  char *relative;
+};
+
+enum {
+  PROP_0,
+  PROP_DISPLAY_NAME,
+  PROP_FILE,
+  PROP_GICON,
+  PROP_IS_DIRECTORY,
+  N_PROPS
+};
+
+G_DEFINE_FINAL_TYPE (GbpFoundFile, gbp_found_file, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static gboolean
+gbp_found_file_get_is_directory (GbpFoundFile *self)
+{
+  g_assert (GBP_IS_FOUND_FILE (self));
+
+  return self->info != NULL ?
+         g_file_info_get_file_type (self->info) == G_FILE_TYPE_DIRECTORY :
+         FALSE;
+}
+
+static void
+gbp_found_file_query_info_cb (GObject      *object,
+                              GAsyncResult *result,
+                              gpointer      user_data)
+{
+  GFile *file = (GFile *)object;
+  g_autoptr(GbpFoundFile) self = user_data;
+
+  g_assert (G_IS_FILE (file));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (GBP_IS_FOUND_FILE (self));
+
+  if (!(self->info = g_file_query_info_finish (file, result, NULL)))
+    return;
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DISPLAY_NAME]);
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_GICON]);
+
+  if (gbp_found_file_get_is_directory (self))
+    g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_IS_DIRECTORY]);
+}
+
+static void
+gbp_found_file_constructed (GObject *object)
+{
+  GbpFoundFile *self = (GbpFoundFile *)object;
+
+  G_OBJECT_CLASS (gbp_found_file_parent_class)->constructed (object);
+
+  if (self->file == NULL)
+    return;
+
+  g_file_query_info_async (self->file,
+                           G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME","
+                           G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON","
+                           G_FILE_ATTRIBUTE_STANDARD_TYPE,
+                           G_FILE_QUERY_INFO_NONE,
+                           G_PRIORITY_LOW,
+                           NULL,
+                           gbp_found_file_query_info_cb,
+                           g_object_ref (self));
+}
+
+static void
+gbp_found_file_dispose (GObject *object)
+{
+  GbpFoundFile *self = (GbpFoundFile *)object;
+
+  g_clear_object (&self->file);
+  g_clear_object (&self->info);
+  g_clear_pointer (&self->relative, g_free);
+
+  G_OBJECT_CLASS (gbp_found_file_parent_class)->dispose (object);
+}
+
+static void
+gbp_found_file_get_property (GObject    *object,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+  GbpFoundFile *self = GBP_FOUND_FILE (object);
+
+  switch (prop_id)
+    {
+    case PROP_DISPLAY_NAME:
+      if (self->relative != NULL)
+        g_value_set_string (value, self->relative);
+      else if (self->info != NULL)
+        g_value_set_string (value, g_file_info_get_attribute_string (self->info, 
G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME));
+      break;
+
+    case PROP_FILE:
+      g_value_set_object (value, self->file);
+      break;
+
+    case PROP_GICON:
+      if (self->info != NULL)
+        g_value_set_object (value, g_file_info_get_attribute_object (self->info, 
G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON));
+      break;
+
+    case PROP_IS_DIRECTORY:
+      g_value_set_boolean (value, gbp_found_file_get_is_directory (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_found_file_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+  GbpFoundFile *self = GBP_FOUND_FILE (object);
+
+  switch (prop_id)
+    {
+    case PROP_FILE:
+      self->file = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_found_file_class_init (GbpFoundFileClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed = gbp_found_file_constructed;
+  object_class->dispose = gbp_found_file_dispose;
+  object_class->get_property = gbp_found_file_get_property;
+  object_class->set_property = gbp_found_file_set_property;
+
+  properties [PROP_FILE] =
+    g_param_spec_object ("file", NULL, NULL,
+                         G_TYPE_FILE,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_DISPLAY_NAME] =
+    g_param_spec_string ("display-name", NULL, NULL,
+                         NULL,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_GICON] =
+    g_param_spec_object ("gicon", NULL, NULL,
+                         G_TYPE_ICON,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_IS_DIRECTORY] =
+    g_param_spec_boolean ("is-directory", NULL, NULL,
+                          FALSE,
+                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gbp_found_file_init (GbpFoundFile *self)
+{
+}
+
+GbpFoundFile *
+gbp_found_file_new (GFile *workdir,
+                    GFile *file)
+{
+  GbpFoundFile *ret;
+
+  g_return_val_if_fail (G_IS_FILE (file), NULL);
+
+  ret = g_object_new (GBP_TYPE_FOUND_FILE,
+                      "file", file,
+                      NULL);
+  ret->relative = g_file_get_relative_path (workdir, file);
+
+  return ret;
+}
+
+void
+gbp_found_file_open (GbpFoundFile *self,
+                     IdeWorkspace *workspace)
+{
+  g_autoptr(IdePanelPosition) position = NULL;
+  IdeWorkbench *workbench;
+
+  IDE_ENTRY;
+
+  g_return_if_fail (GBP_IS_FOUND_FILE (self));
+  g_return_if_fail (IDE_IS_WORKSPACE (workspace));
+
+  workbench = ide_workspace_get_workbench (workspace);
+  position = ide_panel_position_new ();
+
+  ide_workbench_open_async (workbench,
+                            self->file,
+                            NULL,
+                            IDE_BUFFER_OPEN_FLAGS_NONE,
+                            position,
+                            NULL, NULL, NULL);
+
+  IDE_EXIT;
+}
diff --git a/src/plugins/find-other-file/gbp-found-file.h b/src/plugins/find-other-file/gbp-found-file.h
new file mode 100644
index 000000000..f5476e4d2
--- /dev/null
+++ b/src/plugins/find-other-file/gbp-found-file.h
@@ -0,0 +1,36 @@
+/* gbp-found-file.h
+ *
+ * Copyright 2022 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 <libide-gui.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_FOUND_FILE (gbp_found_file_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpFoundFile, gbp_found_file, GBP, FOUND_FILE, GObject)
+
+GbpFoundFile *gbp_found_file_new  (GFile        *workdir,
+                                   GFile        *file);
+void          gbp_found_file_open (GbpFoundFile *self,
+                                   IdeWorkspace *workspace);
+
+G_END_DECLS
diff --git a/src/plugins/find-other-file/gbp-simple-similar-file-locator.c 
b/src/plugins/find-other-file/gbp-simple-similar-file-locator.c
new file mode 100644
index 000000000..6ec8ced66
--- /dev/null
+++ b/src/plugins/find-other-file/gbp-simple-similar-file-locator.c
@@ -0,0 +1,171 @@
+/* gbp-simple-similar-file-locator.c
+ *
+ * Copyright 2022 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-simple-similar-file-locator"
+
+#include "config.h"
+
+#include <libide-projects.h>
+#include <libide-threading.h>
+#include <libide-vcs.h>
+
+#include "gbp-simple-similar-file-locator.h"
+
+struct _GbpSimpleSimilarFileLocator
+{
+  IdeObject parent_instance;
+};
+
+static void
+gbp_simple_similar_file_locator_list_cb (GObject      *object,
+                                         GAsyncResult *result,
+                                         gpointer      user_data)
+{
+  GFile *file = (GFile *)object;
+  g_autoptr(GListStore) store = NULL;
+  g_autoptr(GPtrArray) ar = NULL;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  IdeVcs *vcs;
+
+  IDE_ENTRY;
+
+  g_assert (G_IS_FILE (file));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  if (!(ar = ide_g_file_find_finish (file, result, &error)))
+    {
+      ide_task_return_error (task, g_steal_pointer (&error));
+      IDE_EXIT;
+    }
+
+  vcs = ide_task_get_task_data (task);
+  store = g_list_store_new (G_TYPE_FILE);
+
+  for (guint i = 0; i < ar->len; i++)
+    {
+      GFile *item = g_ptr_array_index (ar, i);
+
+      if (!ide_vcs_is_ignored (vcs, item, NULL))
+        g_list_store_append (store, item);
+    }
+
+  ide_task_return_object (task, g_steal_pointer (&store));
+
+  IDE_EXIT;
+}
+
+static void
+gbp_simple_similar_file_locator_list_async (IdeSimilarFileLocator *locator,
+                                            GFile                 *file,
+                                            GCancellable          *cancellable,
+                                            GAsyncReadyCallback    callback,
+                                            gpointer               user_data)
+{
+  GbpSimpleSimilarFileLocator *self = (GbpSimpleSimilarFileLocator *)locator;
+  g_autofree char *name = NULL;
+  g_autoptr(IdeTask) task = NULL;
+  g_autoptr(GFile) parent = NULL;
+  g_autofree char *pattern = NULL;
+  IdeContext *context;
+  IdeVcs *vcs;
+  char *ptr;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_SIMPLE_SIMILAR_FILE_LOCATOR (self));
+  g_assert (G_IS_FILE (file));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  vcs = ide_vcs_from_context (context);
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, gbp_simple_similar_file_locator_list_async);
+  ide_task_set_task_data (task, g_object_ref (vcs), g_object_unref);
+
+  if (!(parent = g_file_get_parent (file)) ||
+      !(name = g_file_get_basename (file)))
+    {
+      ide_task_return_new_error (task,
+                                 G_IO_ERROR,
+                                 G_IO_ERROR_NOT_SUPPORTED,
+                                 "Unexpected GFile");
+      IDE_EXIT;
+    }
+
+  /* Strip off file suffix */
+  if ((ptr = strrchr (name, '.')))
+    *ptr = 0;
+
+  /* remove -private or private suffix */
+  if (g_str_has_suffix (name, "-private"))
+    name[strlen (name) - strlen ("-private") - 1] = 0;
+
+  /* Simple glob pattern */
+  pattern = g_strdup_printf ("%s*", name);
+
+  ide_g_file_find_with_depth_async (parent,
+                                    pattern,
+                                    2,
+                                    cancellable,
+                                    gbp_simple_similar_file_locator_list_cb,
+                                    g_steal_pointer (&task));
+
+  IDE_EXIT;
+}
+
+static GListModel *
+gbp_simple_similar_file_locator_list_finish (IdeSimilarFileLocator  *locator,
+                                             GAsyncResult           *result,
+                                             GError                **error)
+{
+  GListModel *ret;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_SIMPLE_SIMILAR_FILE_LOCATOR (locator));
+  g_assert (IDE_IS_TASK (result));
+
+  ret = ide_task_propagate_object (IDE_TASK (result), error);
+
+  IDE_RETURN (ret);
+}
+
+static void
+similar_file_locator_iface_init (IdeSimilarFileLocatorInterface *iface)
+{
+  iface->list_async = gbp_simple_similar_file_locator_list_async;
+  iface->list_finish = gbp_simple_similar_file_locator_list_finish;
+}
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (GbpSimpleSimilarFileLocator, gbp_simple_similar_file_locator, IDE_TYPE_OBJECT,
+                               G_IMPLEMENT_INTERFACE (IDE_TYPE_SIMILAR_FILE_LOCATOR, 
similar_file_locator_iface_init))
+
+static void
+gbp_simple_similar_file_locator_class_init (GbpSimpleSimilarFileLocatorClass *klass)
+{
+}
+
+static void
+gbp_simple_similar_file_locator_init (GbpSimpleSimilarFileLocator *self)
+{
+}
diff --git a/src/plugins/find-other-file/gbp-simple-similar-file-locator.h 
b/src/plugins/find-other-file/gbp-simple-similar-file-locator.h
new file mode 100644
index 000000000..1d1d9dc38
--- /dev/null
+++ b/src/plugins/find-other-file/gbp-simple-similar-file-locator.h
@@ -0,0 +1,31 @@
+/* gbp-simple-similar-file-locator.h
+ *
+ * Copyright 2022 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 <libide-core.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_SIMPLE_SIMILAR_FILE_LOCATOR (gbp_simple_similar_file_locator_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpSimpleSimilarFileLocator, gbp_simple_similar_file_locator, GBP, 
SIMPLE_SIMILAR_FILE_LOCATOR, IdeObject)
+
+G_END_DECLS
diff --git a/src/plugins/find-other-file/gtk/keybindings.json 
b/src/plugins/find-other-file/gtk/keybindings.json
new file mode 100644
index 000000000..15c99196c
--- /dev/null
+++ b/src/plugins/find-other-file/gtk/keybindings.json
@@ -0,0 +1 @@
+{ "trigger" : "<Control><Shift>o", "action" : "win.find-other-file", "when" : "canEdit()", "phase" : 
"capture" },
diff --git a/src/plugins/find-other-file/meson.build b/src/plugins/find-other-file/meson.build
index 4df9128b6..7c527d822 100644
--- a/src/plugins/find-other-file/meson.build
+++ b/src/plugins/find-other-file/meson.build
@@ -1,9 +1,16 @@
-install_data('find_other_file.py', install_dir: plugindir)
+plugins_sources += files([
+  'find-other-file-plugin.c',
+  'gbp-simple-similar-file-locator.c',
+  'gbp-find-other-file-browser.c',
+  'gbp-find-other-file-popover.c',
+  'gbp-find-other-file-workspace-addin.c',
+  'gbp-found-file.c',
+])
 
-configure_file(
-          input: 'find-other-file.plugin',
-         output: 'find-other-file.plugin',
-  configuration: config_h,
-        install: true,
-    install_dir: plugindir,
+plugin_find_other_file_resources = gnome.compile_resources(
+  'find-other-file-resources',
+  'find-other-file.gresource.xml',
+  c_name: 'gbp_find_other_file',
 )
+
+plugins_sources += plugin_find_other_file_resources


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