[gnome-builder] session: Make IdeSession handle plumbing so addins only handle content



commit edab82b7b28314c48454952abe297eb4abdb1c24
Author: vanadiae <vanadiae35 gmail com>
Date:   Sat Jan 23 16:51:48 2021 +0100

    session: Make IdeSession handle plumbing so addins only handle content
    
    Currently the IdeSession provides a very bare session handling, where
    pretty much everything needs to be done by the addins, which represents
    a non-trivial amount of work, meaning that it's hard to have several
    addins.
    
    This also introduces problems like not being able to have several addins
    restoring to the workspace's grid, because the restore order isn't
    guaranteed so pages will end up in the wrong places (because the
    restoring isn't handled at one place but at several ones).
    
    So this commit makes the IdeSession handle all this plumbing in the
    scene, so addins only have to handle the content of the IdePage (e.g.
    set editor page to URI foo://path/to/bar, with search set to "foobaz"),
    which makes them much more approachable to write. This also solves the
    issue of restore order, since the IdeSession can first ask all addins
    to restore the pages, then sort it by their position in the grid, then
    add them back in their right position in the grid.

 doc/help/plugins/session.rst                  |  20 +-
 src/libide/editor/ide-editor-private.h        |   3 +
 src/libide/editor/ide-editor-surface.c        | 112 ++++++
 src/libide/gui/ide-session-addin.c            | 181 +++++----
 src/libide/gui/ide-session-addin.h            |  84 ++--
 src/libide/gui/ide-session-private.h          |   7 +-
 src/libide/gui/ide-session.c                  | 543 +++++++++++++++++++++-----
 src/libide/gui/ide-workbench.c                | 108 ++---
 src/plugins/editor/gbp-editor-session-addin.c | 537 ++++++-------------------
 9 files changed, 876 insertions(+), 719 deletions(-)
---
diff --git a/doc/help/plugins/session.rst b/doc/help/plugins/session.rst
index 6eb078bfd..43072801d 100644
--- a/doc/help/plugins/session.rst
+++ b/doc/help/plugins/session.rst
@@ -3,7 +3,7 @@ Session Tracking
 ################
 
 Some plugins may want to save state when the user closes Builder.
-The `Ide.SessionAddin` allows for saving and restoring state when a project is closed or re-opened.
+The `Ide.SessionAddin` allows for saving and restoring state of an `Ide.Page` when a project is closed or 
re-opened.
 
 .. code-block:: python3
 
@@ -17,9 +17,12 @@ The `Ide.SessionAddin` allows for saving and restoring state when a project is c
 
    class MySessionAddin(Ide.Object, Ide.SessionAddin):
 
-       def do_save_async(self, cancellable, callback, data):
+       def can_save_page(self, page):
+           return issubclass(page, My.CustomPage)
+
+       def do_save_page_async(self, page, cancellable, callback, data):
            # Create our async task
-           task = Ide.Task.new(sel, cancellable, callback)
+           task = Ide.Task.new(self, cancellable, callback)
 
            # State is saved as a variant
            task.result = GLib.Variant.new_int(123)
@@ -31,14 +34,17 @@ The `Ide.SessionAddin` allows for saving and restoring state when a project is c
            if task.propagate_boolean():
                return task.result
 
-       def do_restore_async(self, state, cancellable, callback, data):
+       def do_restore_page_async(self, state, cancellable, callback, data):
            # Create our async task
-           task = Ide.Task.new(sel, cancellable, callback)
+           task = Ide.Task.new(self, cancellable, callback)
 
            # state is a GLib.Variant matching what we saved
+           # unpack state here
+
+           my_page = My.CustomPage.new(data_from_state)
 
            # Now complete task
-           task.return_boolean(True)
+           task.return_pointer(my_page)
 
        def do_restore_finish(self, task):
-           return task.propagate_boolean()
+           return task.propagate_pointer()
diff --git a/src/libide/editor/ide-editor-private.h b/src/libide/editor/ide-editor-private.h
index 6e1cbd54d..d43d548bf 100644
--- a/src/libide/editor/ide-editor-private.h
+++ b/src/libide/editor/ide-editor-private.h
@@ -31,6 +31,7 @@
 #include "ide-editor-search.h"
 #include "ide-editor-sidebar.h"
 #include "ide-editor-surface.h"
+#include "ide-session-private.h"
 
 G_BEGIN_DECLS
 
@@ -50,6 +51,8 @@ struct _IdeEditorSurface
   guint                prefocus_had_bottom : 1;
 
   guint                restore_panel : 1;
+
+  IdeSession          *session;
 };
 
 struct _IdeEditorPage
diff --git a/src/libide/editor/ide-editor-surface.c b/src/libide/editor/ide-editor-surface.c
index 2d1507ae9..fefebdf0d 100644
--- a/src/libide/editor/ide-editor-surface.c
+++ b/src/libide/editor/ide-editor-surface.c
@@ -83,6 +83,109 @@ set_reveal_child_without_transition (DzlDockRevealer *revealer,
   dzl_dock_revealer_set_transition_type (revealer, type);
 }
 
+/* As long as there's no individual saving/restoring of each workspace, it's better to
+ * ignore any non primary editor workspace, so opening an other workspace doesn't erase all
+ * the session state of the primary workspace.
+ */
+static gboolean
+is_primary_workspace (IdeEditorSurface *self)
+{
+  g_assert (self != NULL);
+
+  return gtk_widget_get_ancestor (GTK_WIDGET (self), IDE_TYPE_PRIMARY_WORKSPACE) != NULL;
+}
+
+static volatile gboolean async_saving_done = FALSE;
+
+static void
+ide_editor_surface_session_saved_cb (GObject      *object,
+                                     GAsyncResult *result,
+                                     gpointer      user_data)
+{
+  IdeSession *session = (IdeSession *)object;
+  g_autoptr(GError) error = NULL;
+  IdeEditorSurface *surface = user_data;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_SESSION (session));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_EDITOR_SURFACE (surface));
+
+  async_saving_done = TRUE;
+  _ide_editor_surface_set_loading (surface, FALSE);
+
+  if (!ide_session_save_finish (session, result, &error))
+    g_warning ("Couldn't save session: %s", error->message);
+}
+
+static void
+ide_editor_surface_save_session_state (IdeEditorSurface *self)
+{
+  g_autoptr(GMainContext) ctx = NULL;
+
+  if (!is_primary_workspace (self))
+    return;
+
+  _ide_editor_surface_set_loading (self, TRUE);
+
+  /* Do the saving synchronously because if we let the async call finish after
+   * this function, then the editor surface might have already been destroyed
+   * when session addins try to save their pages.
+   */
+  ctx = g_main_context_new ();
+  g_main_context_push_thread_default (ctx);
+  ide_session_save_async (self->session,
+                          self->grid,
+                          NULL,
+                          ide_editor_surface_session_saved_cb,
+                          self);
+  while (!async_saving_done)
+    g_main_context_iteration (ctx, TRUE);
+  g_main_context_pop_thread_default (ctx);
+  async_saving_done = FALSE;
+}
+
+static void
+ide_editor_surface_session_restored_cb (GObject      *object,
+                                        GAsyncResult *result,
+                                        gpointer      user_data)
+{
+  IdeSession *session = (IdeSession *)object;
+  g_autoptr(GError) error = NULL;
+  IdeEditorSurface *surface = user_data;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_SESSION (session));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_EDITOR_SURFACE (surface));
+
+  _ide_editor_surface_set_loading (surface, FALSE);
+
+  if (!ide_session_restore_finish (session, result, &error))
+    g_warning ("Couldn't restore session: %s", error->message);
+}
+
+static void
+ide_editor_surface_restore_session_state (IdeEditorSurface *self)
+{
+  g_autoptr(GSettings) settings = NULL;
+
+  if (!is_primary_workspace (self))
+    return;
+
+  settings = g_settings_new ("org.gnome.builder");
+  if (!g_settings_get_boolean (settings, "restore-previous-files"))
+      return;
+
+  _ide_editor_surface_set_loading (self, TRUE);
+
+  ide_session_restore_async (self->session,
+                             self->grid,
+                             NULL,
+                             ide_editor_surface_session_restored_cb,
+                             self);
+}
+
 static void
 ide_editor_surface_restore_panel_state (IdeEditorSurface *self)
 {
@@ -158,6 +261,7 @@ ide_editor_surface_agree_to_shutdown (IdeSurface *surface)
   g_assert (IDE_IS_EDITOR_SURFACE (self));
 
   ide_editor_surface_save_panel_state (self);
+  ide_editor_surface_save_session_state (self);
 
   return TRUE;
 }
@@ -457,6 +561,7 @@ ide_editor_surface_destroy (GtkWidget *widget)
   g_assert (IDE_IS_EDITOR_SURFACE (self));
 
   g_clear_object (&self->addins);
+  g_clear_object (&self->session);
 
   GTK_WIDGET_CLASS (ide_editor_surface_parent_class)->destroy (widget);
 }
@@ -465,10 +570,15 @@ static void
 ide_editor_surface_realize (GtkWidget *widget)
 {
   IdeEditorSurface *self = (IdeEditorSurface *)widget;
+  IdeWorkbench *workbench;
 
   g_assert (IDE_IS_EDITOR_SURFACE (self));
 
   ide_editor_surface_restore_panel_state (self);
+  workbench = ide_widget_get_workbench (GTK_WIDGET (self));
+  ide_object_append (IDE_OBJECT (ide_workbench_get_context (workbench)),
+                     IDE_OBJECT (self->session));
+  ide_editor_surface_restore_session_state (self);
 
   GTK_WIDGET_CLASS (ide_editor_surface_parent_class)->realize (widget);
 }
@@ -580,6 +690,8 @@ ide_editor_surface_init (IdeEditorSurface *self)
                             G_CALLBACK (ide_editor_surface_create_page),
                             self);
 
+  self->session = ide_session_new ();
+
   sidebar = ide_editor_surface_get_sidebar (self);
   _ide_editor_sidebar_set_open_pages (sidebar, G_LIST_MODEL (self->grid));
 }
diff --git a/src/libide/gui/ide-session-addin.c b/src/libide/gui/ide-session-addin.c
index 576e91023..9c97ae002 100644
--- a/src/libide/gui/ide-session-addin.c
+++ b/src/libide/gui/ide-session-addin.c
@@ -27,146 +27,195 @@
 G_DEFINE_INTERFACE (IdeSessionAddin, ide_session_addin, IDE_TYPE_OBJECT)
 
 static void
-ide_session_addin_real_save_async (IdeSessionAddin     *self,
-                                   IdeWorkbench         *workbench,
-                                   GCancellable        *cancellable,
-                                   GAsyncReadyCallback  callback,
-                                   gpointer             user_data)
+ide_session_addin_real_save_page_async (IdeSessionAddin     *self,
+                                        IdePage             *page,
+                                        GCancellable        *cancellable,
+                                        GAsyncReadyCallback  callback,
+                                        gpointer             user_data)
 {
   g_task_report_new_error (self, callback, user_data,
-                           ide_session_addin_real_save_async,
+                           ide_session_addin_real_save_page_async,
                            G_IO_ERROR,
                            G_IO_ERROR_NOT_SUPPORTED,
                            "Save not supported");
 }
 
 static GVariant *
-ide_session_addin_real_save_finish (IdeSessionAddin  *self,
-                                    GAsyncResult     *result,
-                                    GError          **error)
+ide_session_addin_real_save_page_finish (IdeSessionAddin  *self,
+                                         GAsyncResult     *result,
+                                         GError          **error)
 {
   return g_task_propagate_pointer (G_TASK (result), error);
 }
 
 static void
-ide_session_addin_real_restore_async (IdeSessionAddin     *self,
-                                      IdeWorkbench         *workbench,
-                                      GVariant            *state,
-                                      GCancellable        *cancellable,
-                                      GAsyncReadyCallback  callback,
-                                      gpointer             user_data)
+ide_session_addin_real_restore_page_async (IdeSessionAddin     *self,
+                                           GVariant            *state,
+                                           GCancellable        *cancellable,
+                                           GAsyncReadyCallback  callback,
+                                           gpointer             user_data)
 {
   g_task_report_new_error (self, callback, user_data,
-                           ide_session_addin_real_restore_async,
+                           ide_session_addin_real_restore_page_async,
                            G_IO_ERROR,
                            G_IO_ERROR_NOT_SUPPORTED,
                            "Restore not supported");
 }
 
+static IdePage *
+ide_session_addin_real_restore_page_finish (IdeSessionAddin  *self,
+                                            GAsyncResult     *result,
+                                            GError          **error)
+{
+  return g_task_propagate_pointer (G_TASK (result), error);
+}
+
 static gboolean
-ide_session_addin_real_restore_finish (IdeSessionAddin  *self,
-                                       GAsyncResult     *result,
-                                       GError          **error)
+ide_session_addin_real_can_save_page (IdeSessionAddin *self,
+                                      IdePage         *page)
 {
-  return g_task_propagate_boolean (G_TASK (result), error);
+  return FALSE;
 }
 
 static void
 ide_session_addin_default_init (IdeSessionAddinInterface *iface)
 {
-  iface->save_async = ide_session_addin_real_save_async;
-  iface->save_finish = ide_session_addin_real_save_finish;
-  iface->restore_async = ide_session_addin_real_restore_async;
-  iface->restore_finish = ide_session_addin_real_restore_finish;
+  iface->save_page_async = ide_session_addin_real_save_page_async;
+  iface->save_page_finish = ide_session_addin_real_save_page_finish;
+  iface->restore_page_async = ide_session_addin_real_restore_page_async;
+  iface->restore_page_finish = ide_session_addin_real_restore_page_finish;
+  iface->can_save_page = ide_session_addin_real_can_save_page;
 }
 
 /**
- * ide_session_addin_save_async:
+ * ide_session_addin_save_page_async:
  * @self: a #IdeSessionAddin
- * @workbench: an #IdeWorkbench
+ * @page: an #IdePage supported by ide_session_addin_can_save_page()
  * @cancellable: (nullable): A #GCancellable or %NULL
  * @callback: callback to execute upon completion
  * @user_data: closure data for @callback
  *
- * Asynchronous request to save state about the session.
+ * Asynchronous request to save a page's session state.
  *
- * The resulting state will be provided when restoring the addin
- * at a future time.
+ * The addin implementation must not attempt to save the page's position within its
+ * parent #IdeGrid. Instead it must only save how to restore the content of the page
+ * (e.g. opening URI foo://path/to/file at position X:Y). It is the #IdeSession that
+ * manages the position of the pages, which means session addins implementation can
+ * be much simpler. See also ide_session_addin_save_page_finish().
  *
- * Since: 3.30
+ * The resulting state will be provided when restoring the page
+ * at a future time with ide_session_addin_restore_page_async().
+ *
+ * Since: 41
  */
 void
-ide_session_addin_save_async (IdeSessionAddin     *self,
-                              IdeWorkbench        *workbench,
-                              GCancellable        *cancellable,
-                              GAsyncReadyCallback  callback,
-                              gpointer             user_data)
+ide_session_addin_save_page_async (IdeSessionAddin     *self,
+                                   IdePage             *page,
+                                   GCancellable        *cancellable,
+                                   GAsyncReadyCallback  callback,
+                                   gpointer             user_data)
 {
   g_return_if_fail (IDE_IS_SESSION_ADDIN (self));
-  g_return_if_fail (IDE_IS_WORKBENCH (workbench));
   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
 
-  IDE_SESSION_ADDIN_GET_IFACE (self)->save_async (self, workbench, cancellable, callback, user_data);
+  IDE_SESSION_ADDIN_GET_IFACE (self)->save_page_async (self, page, cancellable, callback, user_data);
 }
 
 /**
- * ide_session_addin_save_finish:
- * @self: a #IdeSessionAddin
+ * ide_session_addin_save_page_finish:
+ * @self: an #IdeSessionAddin
  *
- * Completes an asynchronous request to save session state.
+ * Completes an asynchronous request to save a page's session state.
  *
- * The resulting #GVariant will be used to restore state at a future time.
+ * The resulting #GVariant will be used to restore the page's state at a future time.
+ * It is highly recommended to store the state as a vardict in the result variant, as
+ * it's much more easy to expand the state later and to handle migrations if needed.
+ * See also ide_session_addin_save_page_async().
  *
- * Returns: (transfer full) (nullable): a #GVariant or %NULL.
+ * Returns: (transfer full) (nullable): a #GVariant or %NULL if an error prevented
+ * from saving the page.
  *
- * Since: 3.30
+ * Since: 41
  */
 GVariant *
-ide_session_addin_save_finish (IdeSessionAddin  *self,
-                               GAsyncResult     *result,
-                               GError          **error)
+ide_session_addin_save_page_finish (IdeSessionAddin  *self,
+                                    GAsyncResult     *result,
+                                    GError          **error)
 {
-  g_return_val_if_fail (IDE_IS_SESSION_ADDIN (self), FALSE);
-  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+  g_return_val_if_fail (IDE_IS_SESSION_ADDIN (self), NULL);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
 
-  return IDE_SESSION_ADDIN_GET_IFACE (self)->save_finish (self, result, error);
+  return IDE_SESSION_ADDIN_GET_IFACE (self)->save_page_finish (self, result, error);
 }
 
 /**
- * ide_session_addin_restore_async:
+ * ide_session_addin_restore_page_async:
  * @self: a #IdeSessionAddin
- * @workbench: an #IdeWorkbench
  * @state: a #GVariant of previous state
  * @cancellable: (nullable): A #GCancellable or %NULL
  * @callback: callback to execute upon completion
  * @user_data: closure data for @callback
  *
- * Asynchronous request to restore session state by the addin.
+ * Asynchronously requests that addin @self restore a page's session state with
+ * the provided state, previously saved by this addin using
+ * ide_session_addin_save_page_async(). This only happens when opening a project.
  *
- * Since: 3.30
+ * Since: 41
  */
 void
-ide_session_addin_restore_async (IdeSessionAddin     *self,
-                                 IdeWorkbench        *workbench,
-                                 GVariant            *state,
-                                 GCancellable        *cancellable,
-                                 GAsyncReadyCallback  callback,
-                                 gpointer             user_data)
+ide_session_addin_restore_page_async (IdeSessionAddin     *self,
+                                      GVariant            *state,
+                                      GCancellable        *cancellable,
+                                      GAsyncReadyCallback  callback,
+                                      gpointer             user_data)
 {
   g_return_if_fail (IDE_IS_SESSION_ADDIN (self));
-  g_return_if_fail (IDE_IS_WORKBENCH (workbench));
+  g_return_if_fail (state != NULL);
   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
 
-  IDE_SESSION_ADDIN_GET_IFACE (self)->restore_async (self, workbench, state, cancellable, callback, 
user_data);
+  IDE_SESSION_ADDIN_GET_IFACE (self)->restore_page_async (self, state, cancellable, callback, user_data);
+}
+
+/**
+ * ide_session_addin_restore_page_finish:
+ * @self: a #IdeSessionAddin
+ *
+ * Completes an asynchronous request to restore a page's session state.
+ *
+ * Returns: (transfer full) (nullable): the created page for the saved state, or %NULL if an error
+ * prevented from restoring the page.
+ *
+ * Since: 41
+ */
+IdePage *
+ide_session_addin_restore_page_finish (IdeSessionAddin  *self,
+                                       GAsyncResult     *result,
+                                       GError          **error)
+{
+  g_return_val_if_fail (IDE_IS_SESSION_ADDIN (self), NULL);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+
+  return IDE_SESSION_ADDIN_GET_IFACE (self)->restore_page_finish (self, result, error);
 }
 
+/**
+ * ide_session_addin_can_save_page:
+ * @self: a #IdeSessionAddin
+ * @page: an #IdePage
+ *
+ * Checks whether @self supports saving @page. This is typically done by checking for
+ * its GObject type using `FOO_IS_BAR_PAGE ()` for page types defined in the plugin.
+ *
+ * Returns: whether @self supports saving @page.
+ *
+ * Since: 41
+ */
 gboolean
-ide_session_addin_restore_finish (IdeSessionAddin  *self,
-                                  GAsyncResult     *result,
-                                  GError          **error)
+ide_session_addin_can_save_page (IdeSessionAddin *self,
+                                 IdePage         *page)
 {
   g_return_val_if_fail (IDE_IS_SESSION_ADDIN (self), FALSE);
-  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+  g_return_val_if_fail (IDE_IS_PAGE (page), FALSE);
 
-  return IDE_SESSION_ADDIN_GET_IFACE (self)->restore_finish (self, result, error);
+  return IDE_SESSION_ADDIN_GET_IFACE (self)->can_save_page (self, page);
 }
diff --git a/src/libide/gui/ide-session-addin.h b/src/libide/gui/ide-session-addin.h
index 14425f5e3..f475f032b 100644
--- a/src/libide/gui/ide-session-addin.h
+++ b/src/libide/gui/ide-session-addin.h
@@ -25,59 +25,61 @@
 #endif
 
 #include <libide-core.h>
-
-#include "ide-workbench.h"
+#include <libide-gui.h>
 
 G_BEGIN_DECLS
 
 #define IDE_TYPE_SESSION_ADDIN (ide_session_addin_get_type ())
 
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_41
 G_DECLARE_INTERFACE (IdeSessionAddin, ide_session_addin, IDE, SESSION_ADDIN, IdeObject)
 
 struct _IdeSessionAddinInterface
 {
   GTypeInterface parent;
 
-  void      (*save_async)     (IdeSessionAddin      *self,
-                               IdeWorkbench         *workbench,
-                               GCancellable         *cancellable,
-                               GAsyncReadyCallback   callback,
-                               gpointer              user_data);
-  GVariant *(*save_finish)    (IdeSessionAddin      *self,
-                               GAsyncResult         *result,
-                               GError              **error);
-  void      (*restore_async)  (IdeSessionAddin      *self,
-                               IdeWorkbench         *workbench,
-                               GVariant             *state,
-                               GCancellable         *cancellable,
-                               GAsyncReadyCallback   callback,
-                               gpointer              user_data);
-  gboolean  (*restore_finish) (IdeSessionAddin      *self,
-                               GAsyncResult         *result,
-                               GError              **error);
+  void      (*save_page_async)     (IdeSessionAddin      *self,
+                                    IdePage              *page,
+                                    GCancellable         *cancellable,
+                                    GAsyncReadyCallback   callback,
+                                    gpointer              user_data);
+  GVariant *(*save_page_finish)    (IdeSessionAddin      *self,
+                                    GAsyncResult         *result,
+                                    GError              **error);
+  void      (*restore_page_async)  (IdeSessionAddin      *self,
+                                    GVariant             *state,
+                                    GCancellable         *cancellable,
+                                    GAsyncReadyCallback   callback,
+                                    gpointer              user_data);
+  IdePage  *(*restore_page_finish) (IdeSessionAddin      *self,
+                                    GAsyncResult         *result,
+                                    GError              **error);
+  gboolean  (*can_save_page)       (IdeSessionAddin      *self,
+                                    IdePage              *page);
 };
 
-IDE_AVAILABLE_IN_3_32
-void      ide_session_addin_save_async     (IdeSessionAddin      *self,
-                                            IdeWorkbench         *workbench,
-                                            GCancellable         *cancellable,
-                                            GAsyncReadyCallback   callback,
-                                            gpointer              user_data);
-IDE_AVAILABLE_IN_3_32
-GVariant *ide_session_addin_save_finish    (IdeSessionAddin      *self,
-                                            GAsyncResult         *result,
-                                            GError              **error);
-IDE_AVAILABLE_IN_3_32
-void      ide_session_addin_restore_async  (IdeSessionAddin      *self,
-                                            IdeWorkbench         *workbench,
-                                            GVariant             *state,
-                                            GCancellable         *cancellable,
-                                            GAsyncReadyCallback   callback,
-                                            gpointer              user_data);
-IDE_AVAILABLE_IN_3_32
-gboolean  ide_session_addin_restore_finish (IdeSessionAddin      *self,
-                                            GAsyncResult         *result,
-                                            GError              **error);
+IDE_AVAILABLE_IN_41
+void      ide_session_addin_save_page_async      (IdeSessionAddin      *self,
+                                                  IdePage              *page,
+                                                  GCancellable         *cancellable,
+                                                  GAsyncReadyCallback   callback,
+                                                  gpointer              user_data);
+IDE_AVAILABLE_IN_41
+GVariant *ide_session_addin_save_page_finish     (IdeSessionAddin      *self,
+                                                  GAsyncResult         *result,
+                                                  GError              **error);
+IDE_AVAILABLE_IN_41
+void      ide_session_addin_restore_page_async   (IdeSessionAddin      *self,
+                                                  GVariant             *state,
+                                                  GCancellable         *cancellable,
+                                                  GAsyncReadyCallback   callback,
+                                                  gpointer              user_data);
+IDE_AVAILABLE_IN_41
+IdePage  *ide_session_addin_restore_page_finish  (IdeSessionAddin      *self,
+                                                  GAsyncResult         *result,
+                                                  GError              **error);
+IDE_AVAILABLE_IN_41
+gboolean  ide_session_addin_can_save_page (IdeSessionAddin *self,
+                                           IdePage         *page);
 
 G_END_DECLS
diff --git a/src/libide/gui/ide-session-private.h b/src/libide/gui/ide-session-private.h
index 7a7f343dc..dc0af564a 100644
--- a/src/libide/gui/ide-session-private.h
+++ b/src/libide/gui/ide-session-private.h
@@ -21,8 +21,7 @@
 #pragma once
 
 #include <libide-core.h>
-
-#include "ide-workbench.h"
+#include <libide-gui.h>
 
 G_BEGIN_DECLS
 
@@ -32,7 +31,7 @@ G_DECLARE_FINAL_TYPE (IdeSession, ide_session, IDE, SESSION, IdeObject)
 
 IdeSession *ide_session_new            (void);
 void        ide_session_restore_async  (IdeSession           *self,
-                                        IdeWorkbench         *workbench,
+                                        IdeGrid              *grid,
                                         GCancellable         *cancellable,
                                         GAsyncReadyCallback   callback,
                                         gpointer              user_data);
@@ -40,7 +39,7 @@ gboolean    ide_session_restore_finish (IdeSession           *self,
                                         GAsyncResult         *result,
                                         GError              **error);
 void        ide_session_save_async     (IdeSession           *self,
-                                        IdeWorkbench         *workbench,
+                                        IdeGrid              *grid,
                                         GCancellable         *cancellable,
                                         GAsyncReadyCallback   callback,
                                         gpointer              user_data);
diff --git a/src/libide/gui/ide-session.c b/src/libide/gui/ide-session.c
index 29d5fe81d..3ab54962b 100644
--- a/src/libide/gui/ide-session.c
+++ b/src/libide/gui/ide-session.c
@@ -24,11 +24,14 @@
 
 #include <libpeas/peas.h>
 #include <libide-plugins.h>
+#include <libide-gui.h>
 #include <libide-threading.h>
 
 #include "ide-session-addin.h"
 #include "ide-session-private.h"
 
+#include "ide-gui-private.h"
+
 struct _IdeSession
 {
   IdeObject               parent_instance;
@@ -37,30 +40,42 @@ struct _IdeSession
 
 typedef struct
 {
-  GPtrArray    *addins;
-  GVariantDict  dict;
-  gint          active;
-  guint         dict_needs_clear : 1;
+  GPtrArray      *addins;
+  GVariantBuilder pages_state;
+  guint           active;
 } Save;
 
 typedef struct
 {
-  IdeWorkbench *workbench;
-  GPtrArray    *addins;
-  GVariant     *state;
-  gint          active;
+  GPtrArray *addins;
+  GVariant  *state;
+  IdeGrid   *grid;
+  GArray    *pages;
+  guint      active;
 } Restore;
 
+typedef struct
+{
+  guint            column;
+  guint            row;
+  guint            depth;
+  IdeSessionAddin *addin;
+  GVariant        *state;
+  IdePage         *restored_page;
+} RestoreItem;
+
 G_DEFINE_TYPE (IdeSession, ide_session, IDE_TYPE_OBJECT)
 
 static void
 restore_free (Restore *r)
 {
   g_assert (r != NULL);
+  g_assert (r->active == 0);
 
   g_clear_pointer (&r->addins, g_ptr_array_unref);
   g_clear_pointer (&r->state, g_variant_unref);
-  g_clear_object (&r->workbench);
+  g_clear_pointer (&r->pages, g_array_unref);
+
   g_slice_free (Restore, r);
 }
 
@@ -70,13 +85,36 @@ save_free (Save *s)
   g_assert (s != NULL);
   g_assert (s->active == 0);
 
-  if (s->dict_needs_clear)
-    g_variant_dict_clear (&s->dict);
-
   g_clear_pointer (&s->addins, g_ptr_array_unref);
+
   g_slice_free (Save, s);
 }
 
+static void
+restore_item_clear (RestoreItem *item)
+{
+  g_assert (item != NULL);
+
+  g_clear_pointer (&item->state, g_variant_unref);
+}
+
+static gint
+compare_restore_items (gconstpointer a,
+                       gconstpointer b)
+{
+  const RestoreItem *item_a = a;
+  const RestoreItem *item_b = b;
+  gint ret;
+
+  if (!(ret = item_a->column - item_b->column))
+    {
+      if (!(ret = item_a->row - item_b->row))
+        ret = item_a->depth - item_b->depth;
+    }
+
+  return ret;
+}
+
 static void
 collect_addins_cb (IdeExtensionSetAdapter *set,
                    PeasPluginInfo         *plugin_info,
@@ -145,13 +183,46 @@ ide_session_init (IdeSession *self)
 }
 
 static void
-ide_session_restore_addin_restore_cb (GObject      *object,
-                                      GAsyncResult *result,
-                                      gpointer      user_data)
+restore_pages_to_grid (GArray  *r_items,
+                       IdeGrid *grid)
+{
+  IDE_ENTRY;
+  for (guint i = 0; i < r_items->len; i++)
+    {
+      RestoreItem *item = &g_array_index (r_items, RestoreItem, i);
+      IdeGridColumn *column;
+      IdeFrame *stack;
+
+      /* Ignore pages that couldn't be restored. */
+      if (item->restored_page == NULL)
+        continue;
+
+      /* This relies on the fact that the items are sorted. */
+      column = ide_grid_get_nth_column (grid, item->column);
+      stack = _ide_grid_get_nth_stack_for_column (grid, column, item->row);
+
+      gtk_container_add (GTK_CONTAINER (stack),
+                         GTK_WIDGET (item->restored_page));
+    }
+  IDE_EXIT;
+}
+
+typedef struct
+{
+  IdeTask     *task;
+  RestoreItem *item;
+} RestorePage;
+
+static void
+on_session_addin_page_restored_cb (GObject      *object,
+                                   GAsyncResult *result,
+                                   gpointer      user_data)
 {
   IdeSessionAddin *addin = (IdeSessionAddin *)object;
-  g_autoptr(IdeTask) task = user_data;
+  RestorePage *r_page = user_data;
+  g_autoptr(IdeTask) task = r_page->task;
   g_autoptr(GError) error = NULL;
+  RestoreItem *item = r_page->item;
   Restore *r;
 
   IDE_ENTRY;
@@ -167,26 +238,205 @@ ide_session_restore_addin_restore_cb (GObject      *object,
   g_assert (r->active > 0);
   g_assert (r->state != NULL);
 
-  if (!ide_session_addin_restore_finish (addin, result, &error))
-    g_warning ("%s: %s", G_OBJECT_TYPE_NAME (addin), error->message);
+  if (!(item->restored_page = ide_session_addin_restore_page_finish (addin, result, &error)))
+    g_warning ("Couldn't restore page with addin %s: %s", G_OBJECT_TYPE_NAME (addin), error->message);
 
   r->active--;
 
   if (r->active == 0)
-    ide_task_return_boolean (task, TRUE);
+    {
+      restore_pages_to_grid (r->pages, r->grid);
+
+      ide_task_return_boolean (task, TRUE);
+    }
 
   IDE_EXIT;
 }
 
+static IdeSessionAddin *
+get_addin_for_name (GPtrArray  *addins,
+                    const char *addin_name)
+{
+  GType addin_type = g_type_from_name (addin_name);
+  for (guint i = 0; i < addins->len; i++)
+    {
+      if (G_OBJECT_TYPE (addins->pdata[i]) == addin_type)
+        return addins->pdata[i];
+    }
+  return NULL;
+}
+
 static void
-ide_session_restore_load_cb (GObject      *object,
-                             GAsyncResult *result,
-                             gpointer      user_data)
+load_restore_items (Restore *r,
+                    GArray  *items)
+{
+  GVariantIter iter;
+  RestoreItem item;
+  GVariant *page_state = NULL;
+
+  g_assert (r != NULL);
+  g_assert (r->state != NULL);
+  g_assert (r->addins != NULL);
+  g_assert (items != NULL);
+
+  g_variant_iter_init (&iter, r->state);
+  while ((page_state = g_variant_iter_next_value (&iter)))
+    {
+      const char *addin_name = NULL;
+
+      g_variant_lookup (page_state, "column", "u", &item.column);
+      g_variant_lookup (page_state, "row", "u", &item.row);
+      g_variant_lookup (page_state, "depth", "u", &item.depth);
+      g_variant_lookup (page_state, "addin_name", "&s", &addin_name);
+      g_variant_lookup (page_state, "addin_page_state", "v", &item.state);
+
+      item.addin = get_addin_for_name (r->addins, addin_name);
+      g_array_append_val (items, item);
+
+      g_variant_unref (page_state);
+    }
+}
+
+static GVariant *
+migrate_pre_api_rework (GVariant *pages_variant)
+{
+  GVariantIter iter;
+  const char *uri = NULL;
+  int column, row, depth;
+  /* Freed in the loop. */
+  GVariant *search_variant;
+
+  GVariantDict version_wrapper_dict;
+  GVariantBuilder addins_states;
+
+  g_variant_dict_init (&version_wrapper_dict, NULL);
+  /* Migrate old format to first version of the new format. */
+  g_variant_dict_insert (&version_wrapper_dict, "version", "u", (guint32) 1);
+
+  g_variant_builder_init (&addins_states, G_VARIANT_TYPE_ARRAY);
+
+  g_debug ("Handling migration of the project's session.gvariant, from prior to the Session API rework…");
+
+  g_variant_iter_init (&iter, pages_variant);
+  while (g_variant_iter_next (&iter, "(&siiiv)", &uri, &column, &row, &depth, &search_variant))
+    {
+      GVariantDict addin_state;
+      GVariantDict editor_session_state;
+
+      g_variant_dict_init (&addin_state, NULL);
+      g_variant_dict_insert (&addin_state, "column", "u", (guint32) column);
+      g_variant_dict_insert (&addin_state, "row", "u", (guint32) row);
+      g_variant_dict_insert (&addin_state, "depth", "u", (guint32) depth);
+      g_variant_dict_insert (&addin_state, "addin_name", "s", "GbpEditorSessionAddin");
+
+      /* Since we need to migrate the data for the new API, let's also migrate to a dictionary
+       * instead of a tuple, for greater flexibility and extensibility in the future.
+       */
+      g_variant_dict_init (&editor_session_state, NULL);
+      g_variant_dict_insert (&editor_session_state, "uri", "s", uri);
+      /* Unbox the search_variant since we don't want to bother with multiple levels of variants,
+       * just have an a{sv}
+       */
+      g_variant_dict_insert_value (&editor_session_state, "search", g_variant_get_variant (search_variant));
+      g_variant_dict_insert (&addin_state, "addin_page_state", "v", g_variant_dict_end 
(&editor_session_state));
+      g_variant_builder_add_value (&addins_states, g_variant_dict_end (&addin_state));
+
+      g_variant_unref (search_variant);
+    }
+
+  g_variant_dict_insert_value (&version_wrapper_dict, "data", g_variant_builder_end (&addins_states));
+
+  g_debug ("Successfully migrated old session.gvariant to new format.");
+
+  return g_variant_take_ref (g_variant_dict_end (&version_wrapper_dict));
+}
+
+static GVariant *
+load_state_with_migrations (GBytes *bytes)
+{
+  g_autoptr(GVariant) variant = NULL;
+  /* This is the value of the "data" key in the final @variant. */
+  g_autoptr(GVariant) migrated_state = NULL;
+  GVariantDict state;
+  gboolean fully_migrated = FALSE;
+  GVariant *old_api_state = NULL;
+
+  g_assert (bytes != NULL);
+
+  variant = g_variant_take_ref (g_variant_new_from_bytes (G_VARIANT_TYPE_VARDICT, bytes, FALSE));
+
+  if (!variant)
+    {
+      g_warning ("Couldn't load the array of pages' states from session.gvariant!");
+      return NULL;
+    }
+
+  g_variant_dict_init (&state, variant);
+
+  /* Handle migrations from prior to the Session API rework, where there was only GbpEditorSessionAddin that 
used it */
+  old_api_state = g_variant_dict_lookup_value (&state, "GbpEditorSessionAddin", G_VARIANT_TYPE ("a(siiiv)"));
+  if (old_api_state)
+    migrated_state = migrate_pre_api_rework (old_api_state);
+  else
+    migrated_state = g_steal_pointer (&variant);
+
+  while (!fully_migrated)
+    {
+      guint32 version;
+      g_autoptr(GVariant) versioned_data = NULL;
+
+      if (!g_variant_lookup (migrated_state, "version", "u", &version))
+        {
+          g_warning ("session.gvariant isn't using the old format but doesn't have a version field, so 
cannot load it!");
+          fully_migrated = TRUE;
+          migrated_state = NULL;
+          break;
+        }
+
+      if (!(versioned_data = g_variant_lookup_value (migrated_state, "data", G_VARIANT_TYPE ("aa{sv}"))))
+        {
+          g_warning ("session.gvariant had a version field but the actual versioned data wasn't found, so 
cannot load it!");
+          fully_migrated = TRUE;
+          migrated_state = NULL;
+          break;
+        }
+
+      switch (version)
+        {
+          /* It's the current format so the rest of the code understands it natively. */
+          case 1:
+            migrated_state = g_steal_pointer (&migrated_state);
+            fully_migrated = TRUE;
+            break;
+
+          default:
+            g_warning ("Version %d of session.gvariant data is not known to Builder!", version);
+            migrated_state = NULL;
+            fully_migrated = TRUE;
+        }
+    }
+
+  if (migrated_state)
+    /* The current format (version 1) is an `aa{sv}` (array of dictionaries) with the dict's keys being:
+     * guint32 column, row, depth;
+     * char *addin_name;
+     * GVariant *addin_page_state;
+     */
+    return g_variant_lookup_value (migrated_state, "data", NULL);
+  else
+    return NULL;
+}
+
+static void
+on_session_cache_loaded_cb (GObject      *object,
+                            GAsyncResult *result,
+                            gpointer      user_data)
 {
   GFile *file = (GFile *)object;
   g_autoptr(IdeTask) task = user_data;
   g_autoptr(GError) error = NULL;
   g_autoptr(GBytes) bytes = NULL;
+  GArray *items = NULL;
   GCancellable *cancellable;
   Restore *r;
 
@@ -201,7 +451,7 @@ ide_session_restore_load_cb (GObject      *object,
 
   g_assert (r != NULL);
   g_assert (r->addins != NULL);
-  g_assert (r->active > 0);
+  g_assert (r->addins->len > 0);
   g_assert (r->state == NULL);
   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
 
@@ -220,7 +470,7 @@ ide_session_restore_load_cb (GObject      *object,
       IDE_EXIT;
     }
 
-  r->state = g_variant_new_from_bytes (G_VARIANT_TYPE_VARDICT, bytes, FALSE);
+  r->state = load_state_with_migrations (bytes);
 
   if (r->state == NULL)
     {
@@ -231,26 +481,31 @@ ide_session_restore_load_cb (GObject      *object,
       IDE_EXIT;
     }
 
-  g_assert (r->addins != NULL);
-  g_assert (r->addins->len > 0);
+  items = g_array_new (FALSE, FALSE, sizeof (RestoreItem));
+  g_array_set_clear_func (items, (GDestroyNotify)restore_item_clear);
+  load_restore_items (r, items);
+  r->pages = items;
+  r->active = items->len;
+  g_array_sort (items, compare_restore_items);
 
-  for (guint i = 0; i < r->addins->len; i++)
+  for (guint i = 0; i < items->len; i++)
     {
-      IdeSessionAddin *addin = g_ptr_array_index (r->addins, i);
-      g_autoptr(GVariant) state = NULL;
-
-      g_assert (IDE_IS_SESSION_ADDIN (addin));
-
-      state = g_variant_lookup_value (r->state,
-                                      G_OBJECT_TYPE_NAME (addin),
-                                      NULL);
+      RestoreItem *item = &g_array_index (items, RestoreItem, i);
+      RestorePage *r_page = g_slice_new0 (RestorePage);
+      r_page->task = g_object_ref (task);
+      r_page->item = item;
+
+      ide_session_addin_restore_page_async (item->addin,
+                                            item->state,
+                                            cancellable,
+                                            on_session_addin_page_restored_cb,
+                                            r_page);
+    }
 
-      ide_session_addin_restore_async (addin,
-                                       r->workbench,
-                                       state,
-                                       cancellable,
-                                       ide_session_restore_addin_restore_cb,
-                                       g_object_ref (task));
+  if (r->active == 0)
+    {
+      ide_task_return_boolean (task, TRUE);
+      IDE_EXIT;
     }
 
   IDE_EXIT;
@@ -259,49 +514,51 @@ ide_session_restore_load_cb (GObject      *object,
 /**
  * ide_session_restore_async:
  * @self: an #IdeSession
- * @workbench: an #IdeWorkbench
- * @cancellable: (nullable): a #GCancellbale or %NULL
+ * @grid: an #IdeGrid
+ * @cancellable: (nullable): a #GCancellable or %NULL
  * @callback: the callback to execute upon completion
  * @user_data: user data for callback
  *
  * This function will asynchronously restore the state of the project to
  * the point it was last saved (typically upon shutdown). This includes
- * open documents and editor splits to the degree possible.
+ * open documents and editor splits to the degree possible. Adding support
+ * for a new page type requires implementing an #IdeSessionAddin.
  *
- * Since: 3.30
+ * Since: 41
  */
 void
 ide_session_restore_async (IdeSession          *self,
-                           IdeWorkbench        *workbench,
+                           IdeGrid             *grid,
                            GCancellable        *cancellable,
                            GAsyncReadyCallback  callback,
                            gpointer             user_data)
 {
   g_autoptr(IdeTask) task = NULL;
   g_autoptr(GFile) file = NULL;
+  g_autoptr(GSettings) settings = NULL;
   IdeContext *context;
   Restore *r;
 
   IDE_ENTRY;
 
   g_return_if_fail (IDE_IS_SESSION (self));
-  g_return_if_fail (IDE_IS_WORKBENCH (workbench));
+  g_return_if_fail (IDE_IS_GRID (grid));
   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
 
   task = ide_task_new (self, cancellable, callback, user_data);
   ide_task_set_source_tag (task, ide_session_restore_async);
 
   r = g_slice_new0 (Restore);
-  r->workbench = g_object_ref (workbench);
   r->addins = g_ptr_array_new_with_free_func (g_object_unref);
   ide_extension_set_adapter_foreach (self->addins, collect_addins_cb, r->addins);
-  r->active = r->addins->len;
+  r->grid = grid;
   ide_task_set_task_data (task, r, restore_free);
 
-  if (r->active == 0)
+  settings = g_settings_new ("org.gnome.builder");
+  if (!g_settings_get_boolean (settings, "restore-previous-files"))
     {
       ide_task_return_boolean (task, TRUE);
-      IDE_EXIT;
+      return;
     }
 
   context = ide_object_get_context (IDE_OBJECT (self));
@@ -309,7 +566,7 @@ ide_session_restore_async (IdeSession          *self,
 
   g_file_load_bytes_async (file,
                            cancellable,
-                           ide_session_restore_load_cb,
+                           on_session_cache_loaded_cb,
                            g_steal_pointer (&task));
 
   IDE_EXIT;
@@ -333,9 +590,9 @@ ide_session_restore_finish (IdeSession    *self,
 }
 
 static void
-ide_session_save_cb (GObject      *object,
-                     GAsyncResult *result,
-                     gpointer      user_data)
+on_state_saved_to_cache_file_cb (GObject      *object,
+                                 GAsyncResult *result,
+                                 gpointer      user_data)
 {
   GFile *file = (GFile *)object;
   g_autoptr(GError) error = NULL;
@@ -356,13 +613,57 @@ ide_session_save_cb (GObject      *object,
 }
 
 static void
-ide_session_save_addin_save_cb (GObject      *object,
+get_page_position (IdePage *page,
+                   guint   *out_column,
+                   guint   *out_row,
+                   guint   *out_depth)
+{
+  GtkWidget *frame_pages_stack;
+  GtkWidget *frame;
+  GtkWidget *grid_column;
+  GtkWidget *grid;
+
+  g_assert (IDE_IS_PAGE (page));
+  g_assert (out_column != NULL);
+  g_assert (out_row != NULL);
+  g_assert (out_depth != NULL);
+
+  frame_pages_stack = gtk_widget_get_ancestor (GTK_WIDGET (page), GTK_TYPE_STACK);
+  frame = gtk_widget_get_ancestor (GTK_WIDGET (frame_pages_stack), IDE_TYPE_FRAME);
+  grid_column = gtk_widget_get_ancestor (GTK_WIDGET (frame), IDE_TYPE_GRID_COLUMN);
+  grid = gtk_widget_get_ancestor (GTK_WIDGET (grid_column), IDE_TYPE_GRID);
+
+  gtk_container_child_get (GTK_CONTAINER (frame_pages_stack), GTK_WIDGET (page),
+                           "position", out_depth,
+                           NULL);
+  *out_depth = MAX (*out_depth, 0);
+
+  gtk_container_child_get (GTK_CONTAINER (grid_column), GTK_WIDGET (frame),
+                           "index", out_row,
+                           NULL);
+  *out_row = MAX (*out_row, 0);
+
+  gtk_container_child_get (GTK_CONTAINER (grid), GTK_WIDGET (grid_column),
+                           "index", out_column,
+                           NULL);
+  *out_column = MAX (*out_column, 0);
+}
+
+typedef struct {
+  IdeTask *task;
+  IdePage *page;
+} SavePage;
+
+static void
+on_session_addin_page_saved_cb (GObject      *object,
                                 GAsyncResult *result,
                                 gpointer      user_data)
 {
   IdeSessionAddin *addin = (IdeSessionAddin *)object;
-  g_autoptr(GVariant) variant = NULL;
-  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GVariant) page_state = NULL;
+  SavePage *save_page = user_data;
+  g_autoptr(IdeTask) task = save_page->task;
+  IdePage *page = save_page->page;
   g_autoptr(GError) error = NULL;
   IdeSession *self;
   Save *s;
@@ -378,21 +679,32 @@ ide_session_save_addin_save_cb (GObject      *object,
 
   g_assert (IDE_IS_SESSION (self));
   g_assert (s != NULL);
-  g_assert (s->addins != NULL);
   g_assert (s->active > 0);
 
-  variant = ide_session_addin_save_finish (addin, result, &error);
+  page_state = ide_session_addin_save_page_finish (addin, result, &error);
 
   if (error != NULL)
-    g_warning ("%s: %s", G_OBJECT_TYPE_NAME (addin), error->message);
+    g_warning ("Could not save page with addin %s: %s", G_OBJECT_TYPE_NAME (addin), error->message);
 
-  if (variant != NULL)
+  if (page_state != NULL)
     {
-      g_assert (!g_variant_is_floating (variant));
+      guint frame_column, frame_row, frame_depth;
+      GVariantDict state_dict;
+
+      g_assert (!g_variant_is_floating (page_state));
+
+      get_page_position (page, &frame_column, &frame_row, &frame_depth);
+
+      g_variant_dict_init (&state_dict, NULL);
+      g_variant_dict_insert (&state_dict, "column", "u", frame_column);
+      g_variant_dict_insert (&state_dict, "row", "u", frame_row);
+      g_variant_dict_insert (&state_dict, "depth", "u", frame_depth);
+      g_variant_dict_insert (&state_dict, "addin_name", "s", G_OBJECT_TYPE_NAME (addin));
+      g_variant_dict_insert (&state_dict, "addin_page_state", "v", page_state);
 
-      s->dict_needs_clear = TRUE;
-      g_variant_dict_insert_value (&s->dict, G_OBJECT_TYPE_NAME (addin), variant);
+      g_variant_builder_add_value (&s->pages_state, g_variant_dict_end (&state_dict));
     }
+  g_free (save_page);
 
   s->active--;
 
@@ -401,15 +713,23 @@ ide_session_save_addin_save_cb (GObject      *object,
       g_autoptr(GVariant) state = NULL;
       g_autoptr(GBytes) bytes = NULL;
       g_autoptr(GFile) file = NULL;
-      GCancellable *cancellable;
       IdeContext *context;
-
-      s->dict_needs_clear = FALSE;
-
-      state = g_variant_take_ref (g_variant_dict_end (&s->dict));
+      GVariantDict final_dict;
+      /* It seems that because g_steal_pointer is an inline function it'll return "task" and set it
+       * to NULL _then_ try to get the cancellable from it when using this getter inline in the
+       * function call below. This looks like a bug from GCC/whatever, as they don't evaluate
+       * expressions in the right order…
+       */
+      GCancellable *cancellable = ide_task_get_cancellable (task);
+
+      g_variant_dict_init (&final_dict, NULL);
+      g_variant_dict_insert (&final_dict, "version", "u", (guint32) 1);
+      g_variant_dict_insert_value (&final_dict, "data", g_variant_builder_end (&s->pages_state));
+
+      state = g_variant_ref_sink (g_variant_dict_end (&final_dict));
+      g_debug ("Saving session state for all pages with %s", g_variant_print (state, TRUE));
       bytes = g_variant_get_data_as_bytes (state);
 
-      cancellable = ide_task_get_cancellable (task);
       context = ide_object_get_context (IDE_OBJECT (self));
       file = ide_context_cache_file (context, "session.gvariant", NULL);
 
@@ -422,30 +742,68 @@ ide_session_save_addin_save_cb (GObject      *object,
                                            FALSE,
                                            G_FILE_CREATE_NONE,
                                            cancellable,
-                                           ide_session_save_cb,
+                                           on_state_saved_to_cache_file_cb,
                                            g_steal_pointer (&task));
     }
 
   IDE_EXIT;
 }
 
+static IdeSessionAddin *
+find_suitable_addin_for_page (IdePage   *page,
+                              GPtrArray *addins)
+{
+  for (guint i = 0; i < addins->len; i++)
+    {
+      IdeSessionAddin *addin = g_ptr_array_index (addins, i);
+      if (ide_session_addin_can_save_page (addin, page))
+        return addin;
+    }
+  return NULL;
+}
+
+static void
+foreach_page_in_grid_save_cb (GtkWidget *widget,
+                              gpointer   user_data)
+{
+  IdePage *page = IDE_PAGE (widget);
+  IdeTask *task = user_data;
+  Save *s = ide_task_get_task_data (task);
+  IdeSessionAddin *addin = find_suitable_addin_for_page (page, s->addins);
+  SavePage *save_page = NULL;
+
+  /* It's not a saveable page. */
+  if (addin == NULL)
+    return;
+
+  save_page = g_slice_new0 (SavePage);
+  save_page->task = g_object_ref (task);
+  save_page->page = page;
+
+  ide_session_addin_save_page_async (addin,
+                                     page,
+                                     ide_task_get_cancellable (task),
+                                     on_session_addin_page_saved_cb,
+                                     g_steal_pointer (&save_page));
+}
+
 /**
  * ide_session_save_async:
  * @self: an #IdeSession
- * @workbench: an #IdeWorkbench
+ * @grid: an #IdeGrid
  * @cancellable: (nullable): a #GCancellable, or %NULL
  * @callback: a callback to execute upon completion
  * @user_data: user data for @callback
  *
- * This function will request that various components save their active state
- * so that the project may be restored to the current layout when the project
- * is re-opened at a later time.
+ * This function will save the position and content of the pages in the @grid,
+ * which can then be restored with ide_session_restore_async(), asking the
+ * content of the pages to the appropriate #IdeSessionAddin.
  *
- * Since: 3.30
+ * Since: 41
  */
 void
 ide_session_save_async (IdeSession          *self,
-                        IdeWorkbench        *workbench,
+                        IdeGrid             *grid,
                         GCancellable        *cancellable,
                         GAsyncReadyCallback  callback,
                         gpointer             user_data)
@@ -456,7 +814,7 @@ ide_session_save_async (IdeSession          *self,
   IDE_ENTRY;
 
   g_return_if_fail (IDE_IS_SESSION (self));
-  g_return_if_fail (IDE_IS_WORKBENCH (workbench));
+  g_return_if_fail (IDE_IS_GRID (grid));
   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
 
   task = ide_task_new (self, cancellable, callback, user_data);
@@ -465,32 +823,23 @@ ide_session_save_async (IdeSession          *self,
   s = g_slice_new0 (Save);
   s->addins = g_ptr_array_new_with_free_func (g_object_unref);
   ide_extension_set_adapter_foreach (self->addins, collect_addins_cb, s->addins);
-  s->active = s->addins->len;
-  g_variant_dict_init (&s->dict, NULL);
-  s->dict_needs_clear = TRUE;
+  s->active = ide_grid_count_pages (grid);
+
+  g_variant_builder_init (&s->pages_state, G_VARIANT_TYPE ("aa{sv}"));
   ide_task_set_task_data (task, s, save_free);
 
+  ide_grid_foreach_page (grid,
+                         foreach_page_in_grid_save_cb,
+                         task);
+
+  g_assert (s != NULL);
+
   if (s->active == 0)
     {
       ide_task_return_boolean (task, TRUE);
       IDE_EXIT;
     }
 
-  for (guint i = 0; i < s->addins->len; i++)
-    {
-      IdeSessionAddin *addin = g_ptr_array_index (s->addins, i);
-
-      ide_session_addin_save_async (addin,
-                                    workbench,
-                                    cancellable,
-                                    ide_session_save_addin_save_cb,
-                                    g_object_ref (task));
-    }
-
-  g_assert (s != NULL);
-  g_assert (s->active > 0);
-  g_assert (s->addins->len == s->active);
-
   IDE_EXIT;
 }
 
diff --git a/src/libide/gui/ide-workbench.c b/src/libide/gui/ide-workbench.c
index 61c0981e6..877b9ee06 100644
--- a/src/libide/gui/ide-workbench.c
+++ b/src/libide/gui/ide-workbench.c
@@ -38,7 +38,6 @@
 #include "ide-gui-global.h"
 #include "ide-gui-private.h"
 #include "ide-primary-workspace.h"
-#include "ide-session-private.h"
 #include "ide-workbench.h"
 #include "ide-workbench-addin.h"
 #include "ide-workspace.h"
@@ -75,7 +74,6 @@ struct _IdeWorkbench
   IdeVcs           *vcs;
   IdeVcsMonitor    *vcs_monitor;
   IdeSearchEngine  *search_engine;
-  IdeSession       *session;
 
   /* Various flags */
   guint             unloaded : 1;
@@ -222,10 +220,6 @@ ide_workbench_set_context (IdeWorkbench *self,
     self->build_system = g_object_ref (build_system);
   else
     self->build_system = ide_object_ensure_child_typed (IDE_OBJECT (context), 
IDE_TYPE_FALLBACK_BUILD_SYSTEM);
-
-  /* Setup session monitor for future use */
-  self->session = ide_session_new ();
-  ide_object_append (IDE_OBJECT (self->context), IDE_OBJECT (self->session));
 }
 
 static void
@@ -402,7 +396,6 @@ ide_workbench_finalize (GObject *object)
   g_clear_object (&self->build_system);
   g_clear_object (&self->vcs);
   g_clear_object (&self->search_engine);
-  g_clear_object (&self->session);
   g_clear_object (&self->project_info);
   g_clear_object (&self->cancellable);
   g_clear_object (&self->context);
@@ -966,22 +959,6 @@ ide_workbench_project_loaded_foreach_cb (PeasExtensionSet *set,
   ide_workbench_addin_project_loaded (addin, self->project_info);
 }
 
-static void
-ide_workbench_session_restore_cb (GObject      *object,
-                                  GAsyncResult *result,
-                                  gpointer      user_data)
-{
-  IdeSession *session = (IdeSession *)object;
-  g_autoptr(GError) error = NULL;
-
-  g_assert (IDE_IS_MAIN_THREAD ());
-  g_assert (IDE_IS_SESSION (session));
-  g_assert (G_IS_ASYNC_RESULT (result));
-
-  if (!ide_session_restore_finish (session, result, &error))
-    g_warning ("%s", error->message);
-}
-
 static void
 ide_workbench_load_project_completed (IdeWorkbench *self,
                                       IdeTask      *task)
@@ -1035,15 +1012,6 @@ ide_workbench_load_project_completed (IdeWorkbench *self,
                               ide_workbench_project_loaded_foreach_cb,
                               self);
 
-  /* And now restore the user session, but don't block our task for
-   * it since the greeter is waiting on us.
-   */
-  ide_session_restore_async (self->session,
-                             self,
-                             ide_task_get_cancellable (task),
-                             ide_workbench_session_restore_cb,
-                             NULL);
-
   /* Now that we have a workspace window for the project, we can allow
    * the build manager to start.
    */
@@ -1546,7 +1514,6 @@ ide_workbench_unload_project_completed (IdeWorkbench *self,
                              ide_workbench_unload_foundry_cb,
                              g_object_ref (task));
 }
-
 static void
 ide_workbench_unload_project_cb (GObject      *object,
                                  GAsyncResult *result,
@@ -1582,52 +1549,6 @@ ide_workbench_unload_project_cb (GObject      *object,
     ide_workbench_unload_project_completed (self, task);
 }
 
-static void
-ide_workbench_session_save_cb (GObject      *object,
-                               GAsyncResult *result,
-                               gpointer      user_data)
-{
-  IdeSession *session = (IdeSession *)object;
-  g_autoptr(IdeTask) task = user_data;
-  g_autoptr(GError) error = NULL;
-  IdeWorkbench *self;
-  GPtrArray *addins;
-
-  g_assert (IDE_IS_MAIN_THREAD ());
-  g_assert (IDE_IS_SESSION (session));
-  g_assert (G_IS_ASYNC_RESULT (result));
-  g_assert (IDE_IS_TASK (task));
-
-  /* Not much we can display to the user, as we're tearing widgets down */
-  if (!ide_session_save_finish (session, result, &error))
-    g_warning ("%s", error->message);
-
-  /* Now we can request that each of our addins unload the project. */
-
-  self = ide_task_get_source_object (task);
-  addins = ide_task_get_task_data (task);
-
-  g_assert (IDE_IS_WORKBENCH (self));
-  g_assert (addins != NULL);
-
-  if (addins->len == 0)
-    {
-      ide_workbench_unload_project_completed (self, task);
-      return;
-    }
-
-  for (guint i = 0; i < addins->len; i++)
-    {
-      IdeWorkbenchAddin *addin = g_ptr_array_index (addins, i);
-
-      ide_workbench_addin_unload_project_async (addin,
-                                                self->project_info,
-                                                ide_task_get_cancellable (task),
-                                                ide_workbench_unload_project_cb,
-                                                g_object_ref (task));
-    }
-}
-
 /**
  * ide_workbench_unload_async:
  * @self: an #IdeWorkbench
@@ -1689,19 +1610,34 @@ ide_workbench_unload_async (IdeWorkbench        *self,
    */
   if (self->project_info == NULL)
     {
-      ide_workbench_unload_project_completed (self, task);
+      ide_workbench_unload_project_completed (self, g_steal_pointer (&task));
       return;
     }
 
   addins = ide_workbench_collect_addins (self);
   ide_task_set_task_data (task, g_ptr_array_ref (addins), g_ptr_array_unref);
 
-  /* First unload the session while we are stable */
-  ide_session_save_async (self->session,
-                          self,
-                          cancellable,
-                          ide_workbench_session_save_cb,
-                          g_steal_pointer (&task));
+  if (addins->len == 0)
+    {
+      ide_workbench_unload_project_completed (self, task);
+      return;
+    }
+
+  for (guint i = 0; i < addins->len; i++)
+    {
+      IdeWorkbenchAddin *addin = g_ptr_array_index (addins, i);
+
+      ide_workbench_addin_unload_project_async (addin,
+                                                self->project_info,
+                                                ide_task_get_cancellable (task),
+                                                ide_workbench_unload_project_cb,
+                                                g_object_ref (task));
+    }
+
+  /* Since the g_steal_pointer() just before doesn't always run, ensure the
+   * task isn't freed while it hasn't yet finished running asynchronously.
+   */
+  task = NULL;
 }
 
 /**
diff --git a/src/plugins/editor/gbp-editor-session-addin.c b/src/plugins/editor/gbp-editor-session-addin.c
index 3dace00cb..63690f353 100644
--- a/src/plugins/editor/gbp-editor-session-addin.c
+++ b/src/plugins/editor/gbp-editor-session-addin.c
@@ -40,9 +40,6 @@ struct _GbpEditorSessionAddin
 typedef struct
 {
   gchar *uri;
-  gint   column;
-  gint   row;
-  gint   depth;
   struct {
     gchar    *keyword;
     gboolean  case_sensitive;
@@ -51,195 +48,74 @@ typedef struct
   } search;
 } Item;
 
-typedef struct
-{
-  IdeWorkspace *workspace;
-  GArray       *items;
-  gint          active;
-} LoadState;
-
 static void
-load_state_free (LoadState *state)
-{
-  g_clear_pointer (&state->items, g_array_unref);
-  g_clear_object (&state->workspace);
-  g_slice_free (LoadState, state);
-}
-
-static IdeWorkspace *
-find_workspace (IdeWorkbench *workbench)
-{
-  IdeWorkspace *workspace;
-
-  if (!(workspace = ide_workbench_get_workspace_by_type (workbench, IDE_TYPE_PRIMARY_WORKSPACE)))
-    workspace = ide_workbench_get_workspace_by_type (workbench, IDE_TYPE_EDITOR_WORKSPACE);
-
-  return workspace;
-}
-
-static gint
-compare_item (gconstpointer a,
-              gconstpointer b)
-{
-  const Item *item_a = a;
-  const Item *item_b = b;
-  gint ret;
-
-  if (!(ret = item_a->column - item_b->column))
-    {
-      if (!(ret = item_a->row - item_b->row))
-        ret = item_a->depth - item_b->depth;
-    }
-
-  return ret;
-}
-
-static void
-clear_item (Item *item)
+free_item (Item *item)
 {
   g_clear_pointer (&item->uri, g_free);
   g_clear_pointer (&item->search.keyword, g_free);
+  g_slice_free (Item, item);
 }
 
 static void
-get_view_position (IdePage *view,
-                   gint    *out_column,
-                   gint    *out_row,
-                   gint    *out_depth)
-{
-  GtkWidget *column;
-  GtkWidget *grid;
-  GtkWidget *lstack;
-  GtkWidget *stack;
-  gint depth;
-  gint index_;
-
-  g_assert (IDE_IS_PAGE (view));
-  g_assert (out_column != NULL);
-  g_assert (out_row != NULL);
-
-  *out_column = 0;
-  *out_row = 0;
-  *out_depth = 0;
-
-  stack = gtk_widget_get_ancestor (GTK_WIDGET (view), GTK_TYPE_STACK);
-  lstack = gtk_widget_get_ancestor (GTK_WIDGET (stack), IDE_TYPE_FRAME);
-  column = gtk_widget_get_ancestor (GTK_WIDGET (stack), IDE_TYPE_GRID_COLUMN);
-  grid = gtk_widget_get_ancestor (GTK_WIDGET (column), IDE_TYPE_GRID);
-
-  gtk_container_child_get (GTK_CONTAINER (stack), GTK_WIDGET (view),
-                           "position", &depth,
-                           NULL);
-  *out_depth = MAX (depth, 0);
-
-  gtk_container_child_get (GTK_CONTAINER (column), GTK_WIDGET (lstack),
-                           "index", &index_,
-                           NULL);
-  *out_row = MAX (index_, 0);
-
-  gtk_container_child_get (GTK_CONTAINER (grid), GTK_WIDGET (column),
-                           "index", &index_,
-                           NULL);
-  *out_column = MAX (index_, 0);
-}
-
-static void
-gbp_editor_session_addin_foreach_page_cb (GtkWidget *widget,
-                                          gpointer   user_data)
-{
-  IdePage *view = (IdePage *)widget;
-  GArray *items = user_data;
-
-  g_assert (IDE_IS_PAGE (view));
-  g_assert (items != NULL);
-
-  if (IDE_IS_EDITOR_PAGE (view))
-    {
-      IdeBuffer *buffer = ide_editor_page_get_buffer (IDE_EDITOR_PAGE (view));
-      GFile *file = ide_buffer_get_file (buffer);
-      IdeEditorSearch *search = ide_editor_page_get_search (IDE_EDITOR_PAGE (view));
-      Item item = { 0 };
-
-      if (!ide_buffer_get_is_temporary (buffer))
-        {
-          item.uri = g_file_get_uri (file);
-          get_view_position (view, &item.column, &item.row, &item.depth);
-
-          item.search.keyword = g_strdup (ide_editor_search_get_search_text (search));
-          item.search.at_word_boundaries = ide_editor_search_get_at_word_boundaries (search);
-          item.search.case_sensitive = ide_editor_search_get_case_sensitive (search);
-          item.search.regex_enabled = ide_editor_search_get_regex_enabled (search);
-
-          IDE_TRACE_MSG ("%u:%u:%u: %s", item.column, item.row, item.depth, item.uri);
-
-          g_array_append_val (items, item);
-        }
-    }
-}
-
-static void
-gbp_editor_session_addin_save_async (IdeSessionAddin     *addin,
-                                     IdeWorkbench        *workbench,
-                                     GCancellable        *cancellable,
-                                     GAsyncReadyCallback  callback,
-                                     gpointer             user_data)
+gbp_editor_session_addin_save_page_async (IdeSessionAddin     *addin,
+                                          IdePage             *page,
+                                          GCancellable        *cancellable,
+                                          GAsyncReadyCallback  callback,
+                                          gpointer             user_data)
 {
   g_autoptr(IdeTask) task = NULL;
-  g_autoptr(GArray) items = NULL;
-  GVariantBuilder builder;
+  IdeBuffer *buffer = ide_editor_page_get_buffer (IDE_EDITOR_PAGE (page));
+  GFile *file = ide_buffer_get_file (buffer);
 
   IDE_ENTRY;
 
   g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (IDE_IS_SESSION_ADDIN (addin));
-  g_assert (IDE_IS_WORKBENCH (workbench));
+  g_assert (IDE_IS_PAGE (page));
   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
 
   task = ide_task_new (addin, cancellable, callback, user_data);
-  ide_task_set_source_tag (task, gbp_editor_session_addin_save_async);
-
-  items = g_array_new (FALSE, FALSE, sizeof (Item));
-  g_array_set_clear_func (items, (GDestroyNotify)clear_item);
-
-  ide_workbench_foreach_page (workbench,
-                              gbp_editor_session_addin_foreach_page_cb,
-                              items);
-
-  g_array_sort (items, compare_item);
+  ide_task_set_source_tag (task, gbp_editor_session_addin_save_page_async);
 
-  g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(siiiv)"));
-
-  for (guint i = 0; i < items->len; i++)
+  if (!ide_buffer_get_is_temporary (buffer))
     {
-      const Item *item = &g_array_index (items, Item, i);
-      GVariantBuilder sub;
-
-      g_variant_builder_init (&sub, G_VARIANT_TYPE ("a{sv}"));
-      g_variant_builder_add_parsed (&sub, "{'search.keyword',<%s>}",item->search.keyword ?: "");
-      g_variant_builder_add_parsed (&sub, "{'search.at-word-boundaries',<%b>}", 
item->search.at_word_boundaries);
-      g_variant_builder_add_parsed (&sub, "{'search.regex-enabled',<%b>}", item->search.regex_enabled);
-      g_variant_builder_add_parsed (&sub, "{'search.case-sensitive',<%b>}", item->search.case_sensitive);
-
-      g_variant_builder_add (&builder,
-                             "(siiiv)",
-                             item->uri,
-                             item->column,
-                             item->row,
-                             item->depth,
-                             g_variant_new_variant (g_variant_builder_end (&sub)));
+      GVariantDict state_dict;
+      IdeEditorSearch *search = ide_editor_page_get_search (IDE_EDITOR_PAGE (page));
+      GVariantDict search_dict;
+      const char *search_keyword = ide_editor_search_get_search_text (search);
+      g_autofree char *uri = g_file_get_uri (file);
+      GVariant *v;
+
+      g_variant_dict_init (&search_dict, NULL);
+      g_variant_dict_insert (&search_dict, "search.keyword", "s", search_keyword ? search_keyword : "");
+      g_variant_dict_insert (&search_dict, "search.at-word-boundaries", "b", 
ide_editor_search_get_at_word_boundaries (search));
+      g_variant_dict_insert (&search_dict, "search.regex-enabled", "b", ide_editor_search_get_regex_enabled 
(search));
+      g_variant_dict_insert (&search_dict, "search.case-sensitive", "b", 
ide_editor_search_get_case_sensitive (search));
+
+      g_variant_dict_init (&state_dict, NULL);
+      g_variant_dict_insert (&state_dict, "uri", "s", uri);
+      g_variant_dict_insert_value (&state_dict, "search", g_variant_dict_end (&search_dict));
+
+      v = g_variant_dict_end (&state_dict);
+      g_debug ("Saved editor page: %s", g_variant_print (v, TRUE));
+
+      ide_task_return_pointer (task, g_variant_take_ref (v), g_variant_unref);
+    }
+  else
+    {
+      ide_task_return_new_error (task,
+                                 G_FILE_ERROR,
+                                 G_FILE_ERROR_FAILED,
+                                 "Can't save page as it's a temporary buffer");
     }
-
-  ide_task_return_pointer (task,
-                           g_variant_take_ref (g_variant_builder_end (&builder)),
-                           g_variant_unref);
 
   IDE_EXIT;
 }
 
 static GVariant *
-gbp_editor_session_addin_save_finish (IdeSessionAddin  *self,
-                                      GAsyncResult     *result,
-                                      GError          **error)
+gbp_editor_session_addin_save_page_finish (IdeSessionAddin  *self,
+                                           GAsyncResult     *result,
+                                           GError          **error)
 {
   g_assert (GBP_IS_EDITOR_SESSION_ADDIN (self));
   g_assert (IDE_IS_TASK (result));
@@ -247,309 +123,134 @@ gbp_editor_session_addin_save_finish (IdeSessionAddin  *self,
   return ide_task_propagate_pointer (IDE_TASK (result), error);
 }
 
-static void
-load_state_finish (GbpEditorSessionAddin *self,
-                   LoadState             *state)
-{
-  IdeBufferManager *bufmgr;
-  IdeContext *context;
-  IdeSurface *editor;
-  IdeGrid *grid;
-
-  g_assert (GBP_IS_EDITOR_SESSION_ADDIN (self));
-  g_assert (state != NULL);
-
-  context = ide_object_get_context (IDE_OBJECT (self));
-  bufmgr = ide_buffer_manager_from_context (context);
-
-  editor = ide_workspace_get_surface_by_name (state->workspace, "editor");
-  grid = ide_editor_surface_get_grid (IDE_EDITOR_SURFACE (editor));
-
-  /* Now restore views in the proper place */
-
-  for (guint i = 0; i < state->items->len; i++)
-    {
-      const Item *item = &g_array_index (state->items, Item, i);
-      g_autoptr(GFile) file = NULL;
-      IdeGridColumn *column;
-      IdeEditorSearch *search;
-      IdeFrame *stack;
-      IdeEditorPage *view;
-      IdeBuffer *buffer;
-
-      file = g_file_new_for_uri (item->uri);
-
-      if (!(buffer = ide_buffer_manager_find_buffer (bufmgr, file)))
-        {
-          g_warning ("Failed to restore %s", item->uri);
-          continue;
-        }
-
-      column = ide_grid_get_nth_column (grid, item->column);
-      stack = _ide_grid_get_nth_stack_for_column (grid, column, item->row);
-
-      view = g_object_new (IDE_TYPE_EDITOR_PAGE,
-                           "buffer", buffer,
-                           "visible", TRUE,
-                           NULL);
-
-      search = ide_editor_page_get_search (view);
-
-      ide_editor_search_set_search_text (search, item->search.keyword);
-      ide_editor_search_set_at_word_boundaries (search, item->search.at_word_boundaries);
-      ide_editor_search_set_case_sensitive (search, item->search.case_sensitive);
-      ide_editor_search_set_regex_enabled (search, item->search.regex_enabled);
-
-      gtk_container_add (GTK_CONTAINER (stack), GTK_WIDGET (view));
-    }
-}
-
 static void
 gbp_editor_session_addin_load_file_cb (GObject      *object,
                                        GAsyncResult *result,
                                        gpointer      user_data)
 {
   IdeBufferManager *bufmgr = (IdeBufferManager *)object;
-  g_autoptr(IdeBuffer) loaded = NULL;
+  g_autoptr(IdeBuffer) buffer = NULL;
   g_autoptr(IdeTask) task = user_data;
   g_autoptr(GError) error = NULL;
   GbpEditorSessionAddin *self;
-  LoadState *state;
+  IdeEditorPage *page;
+  IdeEditorSearch *search;
+  Item *item;
 
   g_assert (IDE_IS_BUFFER_MANAGER (bufmgr));
   g_assert (G_IS_ASYNC_RESULT (result));
   g_assert (IDE_IS_TASK (task));
 
-  if (!(loaded = ide_buffer_manager_load_file_finish (bufmgr, result, &error)))
-    g_warning ("Failed to load buffer: %s", error->message);
-
-  state = ide_task_get_task_data (task);
-  self = ide_task_get_source_object (task);
-
-  g_assert (state != NULL);
-  g_assert (state->items != NULL);
-  g_assert (state->active > 0);
-  g_assert (GBP_IS_EDITOR_SESSION_ADDIN (self));
-
-  state->active--;
-
-  if (state->active == 0)
+  if (!(buffer = ide_buffer_manager_load_file_finish (bufmgr, result, &error)))
     {
-      load_state_finish (self, state);
-      ide_task_return_boolean (task, TRUE);
-    }
-}
+      g_warning ("Failed to load buffer: %s", error->message);
 
-static void
-restore_file (GObject      *source,
-              GAsyncResult *result,
-              gpointer      user_data)
-{
-  GFile *file = (GFile *)source;
-  g_autoptr(IdeTask) task = user_data;
-  g_autoptr(GError) error = NULL;
-  g_autoptr(GFileInfo) info = NULL;
-  GbpEditorSessionAddin *self;
-  LoadState *load_state;
-
-  IDE_ENTRY;
+      ide_task_return_new_error (task,
+                                 error->domain,
+                                 error->code,
+                                 "Couldn't load buffer: %s", error->message);
 
-  g_assert (G_IS_FILE (file));
-  g_assert (G_IS_ASYNC_RESULT (result));
-  g_assert (IDE_IS_TASK (task));
+      return;
+    }
 
+  item = ide_task_get_task_data (task);
   self = ide_task_get_source_object (task);
-  load_state = ide_task_get_task_data (task);
 
+  g_assert (item != NULL);
   g_assert (GBP_IS_EDITOR_SESSION_ADDIN (self));
-  g_assert (load_state != NULL);
 
-  if ((info = g_file_query_info_finish (file, result, &error)))
-    {
-      IdeContext *context = ide_object_get_context (IDE_OBJECT (self));
-      IdeBufferManager *bufmgr = ide_buffer_manager_from_context (context);
-
-      ide_buffer_manager_load_file_async (bufmgr,
-                                          file,
-                                          IDE_BUFFER_OPEN_FLAGS_NO_VIEW,
-                                          NULL,
-                                          ide_task_get_cancellable (task),
-                                          gbp_editor_session_addin_load_file_cb,
-                                          g_object_ref (task));
-    }
-  else
-    {
-      load_state->active--;
+  page = g_object_new (IDE_TYPE_EDITOR_PAGE,
+                       "buffer", buffer,
+                       "visible", TRUE,
+                       NULL);
 
-      if (load_state->active == 0)
-        {
-          load_state_finish (self, load_state);
-          ide_task_return_boolean (task, TRUE);
-        }
-    }
-
-  IDE_EXIT;
-}
+  search = ide_editor_page_get_search (page);
 
-static void
-load_task_completed_cb (IdeTask          *task,
-                        GParamSpec       *pspec,
-                        IdeEditorSurface *surface)
-{
-  g_assert (IDE_IS_TASK (task));
-  g_assert (IDE_IS_EDITOR_SURFACE (surface));
+  ide_editor_search_set_search_text (search, item->search.keyword);
+  ide_editor_search_set_at_word_boundaries (search, item->search.at_word_boundaries);
+  ide_editor_search_set_case_sensitive (search, item->search.case_sensitive);
+  ide_editor_search_set_regex_enabled (search, item->search.regex_enabled);
 
-  /* Always show the grid after the task completes */
-  _ide_editor_surface_set_loading (surface, FALSE);
+  ide_task_return_pointer (task, page, g_object_unref);
 }
 
 static void
-gbp_editor_session_addin_restore_async (IdeSessionAddin     *addin,
-                                        IdeWorkbench        *workbench,
-                                        GVariant            *state,
-                                        GCancellable        *cancellable,
-                                        GAsyncReadyCallback  callback,
-                                        gpointer             user_data)
+gbp_editor_session_addin_restore_page_async (IdeSessionAddin     *addin,
+                                             GVariant            *state,
+                                             GCancellable        *cancellable,
+                                             GAsyncReadyCallback  callback,
+                                             gpointer             user_data)
 {
   g_autoptr(IdeTask) task = NULL;
-  g_autoptr(GHashTable) uris = NULL;
-  g_autoptr(GSettings) settings = NULL;
-  const gchar *uri;
-  LoadState *load_state;
-  IdeWorkspace *workspace;
-  IdeSurface *editor;
-  GVariantIter iter;
-  GVariant *extra = NULL;
-  const gchar *format = "(&siii)";
-  gint column, row, depth;
+  GVariantDict state_dict;
+  g_autoptr(GVariant) extra = NULL;
+  g_autoptr(GFile) file = NULL;
+  IdeContext *context;
+  IdeBufferManager *bufmgr;
+  Item *item = NULL;
 
   g_assert (GBP_IS_EDITOR_SESSION_ADDIN (addin));
   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_assert (g_variant_is_of_type (state, G_VARIANT_TYPE_VARDICT));
 
   task = ide_task_new (addin, cancellable, callback, user_data);
-  ide_task_set_source_tag (task, gbp_editor_session_addin_restore_async);
-
-  if (state == NULL)
-    {
-      ide_task_return_boolean (task, TRUE);
-      return;
-    }
-
-  if (!(workspace = find_workspace (workbench)) ||
-      !(editor = ide_workspace_get_surface_by_name (workspace, "editor")))
-    {
-      ide_task_return_new_error (task,
-                                 G_IO_ERROR,
-                                 G_IO_ERROR_FAILED,
-                                 "NO editor surface to restore documents");
-      return;
-    }
-
-  g_signal_connect_object (task,
-                           "notify::completed",
-                           G_CALLBACK (load_task_completed_cb),
-                           editor,
-                           0);
-
-  settings = g_settings_new ("org.gnome.builder");
-
-  if (!g_settings_get_boolean (settings, "restore-previous-files"))
-    {
-      ide_task_return_boolean (task, TRUE);
-      return;
-    }
-
-  uris = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
-
-  load_state = g_slice_new0 (LoadState);
-  load_state->items = g_array_new (FALSE, FALSE, sizeof (Item));
-  load_state->workspace = g_object_ref (workspace);
-  g_array_set_clear_func (load_state->items, (GDestroyNotify)clear_item);
-  ide_task_set_task_data (task, load_state, load_state_free);
-
-  g_variant_iter_init (&iter, state);
-
-  load_state->active++;
-
-  if (g_variant_is_of_type (state, G_VARIANT_TYPE ("a(siiiv)")))
-    format = "(&siiiv)";
-
-  while (g_variant_iter_next (&iter, format, &uri, &column, &row, &depth, &extra))
-    {
-      g_autoptr(GFile) gfile = NULL;
-      Item item = {0};
-
-      IDE_TRACE_MSG ("Restore URI \"%s\" at %d:%d:%d", uri, column, row, depth);
-
-      item.uri = g_strdup (uri);
-      item.column = column;
-      item.row = row;
-      item.depth = depth;
+  ide_task_set_source_tag (task, gbp_editor_session_addin_restore_page_async);
 
-      if (extra != NULL)
-        {
-          g_autoptr(GVariantDict) dict = NULL;
+  item = g_slice_new0 (Item);
+  ide_task_set_task_data (task, item, free_item);
 
-          dict = g_variant_dict_new (g_variant_get_variant (extra));
+  g_variant_dict_init (&state_dict, state);
+  g_variant_dict_lookup (&state_dict, "uri", "s", &item->uri);
+  extra = g_variant_dict_lookup_value (&state_dict, "search", G_VARIANT_TYPE_VARDICT);
+  g_assert (extra != NULL);
+  g_assert (item->uri != NULL);
 
-          g_variant_dict_lookup (dict, "search.keyword", "s", &item.search.keyword);
-          g_variant_dict_lookup (dict, "search.at-word-boundaries", "b", &item.search.at_word_boundaries);
-          g_variant_dict_lookup (dict, "search.case-sensitive", "b", &item.search.case_sensitive);
-          g_variant_dict_lookup (dict, "search.regex-enabled", "b", &item.search.regex_enabled);
-        }
+  IDE_TRACE_MSG ("Restoring editor page URI \"%s\"", item->uri);
 
-      g_array_append_val (load_state->items, item);
+  g_variant_lookup (extra, "search.keyword", "s", &item->search.keyword);
+  g_variant_lookup (extra, "search.at-word-boundaries", "b", &item->search.at_word_boundaries);
+  g_variant_lookup (extra, "search.case-sensitive", "b", &item->search.case_sensitive);
+  g_variant_lookup (extra, "search.regex-enabled", "b", &item->search.regex_enabled);
 
-      /* Skip loading buffer if already loading */
-      if (!g_hash_table_contains (uris, uri))
-        {
-          g_hash_table_add (uris, g_strdup (uri));
-          gfile = g_file_new_for_uri (uri);
-
-          load_state->active++;
-
-          g_file_query_info_async (gfile,
-                                   G_FILE_ATTRIBUTE_STANDARD_NAME,
-                                   G_FILE_QUERY_INFO_NONE,
-                                   G_PRIORITY_LOW,
-                                   cancellable,
-                                   restore_file,
-                                   g_object_ref (task));
-        }
-
-      g_clear_pointer (&extra, g_variant_unref);
-    }
-
-  load_state->active--;
-
-  if (load_state->active == 0)
-    {
-      ide_task_return_boolean (task, TRUE);
-      return;
-    }
+  file = g_file_new_for_uri (item->uri);
+  context = ide_object_get_context (IDE_OBJECT (addin));
+  bufmgr = ide_buffer_manager_from_context (context);
 
-  /* Hide the grid until we've loaded */
-  _ide_editor_surface_set_loading (IDE_EDITOR_SURFACE (editor), TRUE);
+  ide_buffer_manager_load_file_async (bufmgr,
+                                      file,
+                                      IDE_BUFFER_OPEN_FLAGS_NO_VIEW,
+                                      NULL,
+                                      ide_task_get_cancellable (task),
+                                      gbp_editor_session_addin_load_file_cb,
+                                      g_object_ref (task));
 }
 
-static gboolean
-gbp_editor_session_addin_restore_finish (IdeSessionAddin  *self,
-                                         GAsyncResult     *result,
-                                         GError          **error)
+static IdePage *
+gbp_editor_session_addin_restore_page_finish (IdeSessionAddin  *self,
+                                              GAsyncResult     *result,
+                                              GError          **error)
 {
   g_assert (GBP_IS_EDITOR_SESSION_ADDIN (self));
   g_assert (IDE_IS_TASK (result));
 
-  return ide_task_propagate_boolean (IDE_TASK (result), error);
+  return ide_task_propagate_pointer (IDE_TASK (result), error);
+}
+
+static gboolean
+gbp_editor_session_addin_can_save_page (IdeSessionAddin *addin,
+                                        IdePage         *page)
+{
+  return IDE_IS_EDITOR_PAGE (page);
 }
 
 static void
 session_addin_iface_init (IdeSessionAddinInterface *iface)
 {
-  iface->save_async = gbp_editor_session_addin_save_async;
-  iface->save_finish = gbp_editor_session_addin_save_finish;
-  iface->restore_async = gbp_editor_session_addin_restore_async;
-  iface->restore_finish = gbp_editor_session_addin_restore_finish;
+  iface->save_page_async = gbp_editor_session_addin_save_page_async;
+  iface->save_page_finish = gbp_editor_session_addin_save_page_finish;
+  iface->restore_page_async = gbp_editor_session_addin_restore_page_async;
+  iface->restore_page_finish = gbp_editor_session_addin_restore_page_finish;
+  iface->can_save_page = gbp_editor_session_addin_can_save_page;
 }
 
 G_DEFINE_TYPE_WITH_CODE (GbpEditorSessionAddin, gbp_editor_session_addin, IDE_TYPE_OBJECT,


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