[gnome-builder] libide-editor: port to GTK 4



commit 40e8a3b3ad6090e14cab87b70aacc63a27485513
Author: Christian Hergert <chergert redhat com>
Date:   Mon Jul 11 22:00:43 2022 -0700

    libide-editor: port to GTK 4
    
     - Remove use of IdeSurface (we don't have surfaces anymore)
     - Rewrite IdeEditorPage using more code from Text Editor
     - Cleanup shortcuts
     - Delete lots of janky code (some may come back)
     - Remove settings dialog (plugins will provide alternative UI)
     - Remove sidebar/utilities/etc
     - Add encoding/line-ending support
     - Cleanup open/discovery code to use IdePanelPosition
    
    There is still a lot missing from this port. Search has not yet been ported
    over from Text Editor for example.

 src/libide/editor/gtk/menus.ui                     |   74 +
 src/libide/editor/ide-editor-addin.c               |  160 --
 src/libide/editor/ide-editor-addin.h               |   64 -
 ...e-editor-plugin-private.h => ide-editor-init.c} |   21 +-
 src/libide/editor/ide-editor-page-actions.c        |  605 +-----
 src/libide/editor/ide-editor-page-addin.c          |    8 +-
 src/libide/editor/ide-editor-page-addin.h          |   15 +-
 src/libide/editor/ide-editor-page-private.h        |   59 +
 src/libide/editor/ide-editor-page-settings.c       |  359 ++--
 src/libide/editor/ide-editor-page-shortcuts.c      |  153 --
 src/libide/editor/ide-editor-page.c                | 1495 +++++----------
 src/libide/editor/ide-editor-page.h                |   83 +-
 src/libide/editor/ide-editor-page.ui               |  126 +-
 src/libide/editor/ide-editor-print-operation.c     |  210 --
 src/libide/editor/ide-editor-print-operation.h     |   33 -
 src/libide/editor/ide-editor-private.h             |   85 +-
 .../editor/ide-editor-search-bar-shortcuts.c       |   72 -
 src/libide/editor/ide-editor-search-bar.c          |  661 -------
 src/libide/editor/ide-editor-search-bar.h          |   44 -
 src/libide/editor/ide-editor-search-bar.ui         |  283 ---
 src/libide/editor/ide-editor-search.c              | 2025 --------------------
 src/libide/editor/ide-editor-search.h              |  146 --
 src/libide/editor/ide-editor-settings-dialog.c     |  335 ----
 src/libide/editor/ide-editor-settings-dialog.h     |   34 -
 src/libide/editor/ide-editor-settings-dialog.ui    |  295 ---
 src/libide/editor/ide-editor-sidebar.c             |  557 ------
 src/libide/editor/ide-editor-sidebar.h             |   54 -
 src/libide/editor/ide-editor-sidebar.ui            |  113 --
 src/libide/editor/ide-editor-surface-actions.c     |  164 --
 src/libide/editor/ide-editor-surface-shortcuts.c   |  113 --
 src/libide/editor/ide-editor-surface.c             | 1093 -----------
 src/libide/editor/ide-editor-surface.h             |   65 -
 src/libide/editor/ide-editor-surface.ui            |   36 -
 src/libide/editor/ide-editor-utilities.c           |   85 -
 src/libide/editor/ide-editor-utils.c               |  310 +++
 src/libide/editor/ide-editor-utils.h               |   41 +
 src/libide/editor/ide-editor-workspace.c           |  234 ++-
 src/libide/editor/ide-editor-workspace.h           |    8 +-
 src/libide/editor/ide-editor-workspace.ui          |  117 +-
 src/libide/editor/ide-editor.c                     |  260 +++
 .../{ide-editor-utilities.h => ide-editor.h}       |   23 +-
 src/libide/editor/libide-editor.gresource.xml      |    8 +-
 src/libide/editor/libide-editor.h                  |   24 +-
 src/libide/editor/meson.build                      |   29 +-
 src/libide/editor/style.css                        |   12 +
 45 files changed, 1817 insertions(+), 8974 deletions(-)
---
diff --git a/src/libide/editor/gtk/menus.ui b/src/libide/editor/gtk/menus.ui
new file mode 100644
index 000000000..714b35ec7
--- /dev/null
+++ b/src/libide/editor/gtk/menus.ui
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <menu id="ide-editor-workspace-menu">
+    <section id="ide-editor-workspace-menu-theme-section">
+      <item>
+        <attribute name="custom">theme_selector</attribute>
+      </item>
+    </section>
+    <section id="ide-editor-workspace-menu-projects-section"/>
+    <section id="ide-editor-workspace-menu-placeholder1"/>
+    <section id="ide-editor-workspace-menu-placeholder2"/>
+    <section id="ide-editor-workspace-menu-open-section">
+      <attribute name="id">ide-editor-workspace-menu-open-section</attribute>
+      <item>
+        <attribute name="id">ide-editor-workspace-menu-open</attribute>
+        <attribute name="label" translatable="yes">Open Fileā€¦</attribute>
+        <attribute name="action">workbench.open</attribute>
+        <attribute name="accel">&lt;primary&gt;o</attribute>
+      </item>
+    </section>
+    <section id="ide-editor-workspace-menu-app-section">
+      <attribute name="id">ide-editor-workspace-menu-app-section</attribute>
+      <item>
+        <attribute name="id">ide-editor-workspace-menu-preferences</attribute>
+        <attribute name="label" translatable="yes">Preferences</attribute>
+        <attribute name="action">app.preferences</attribute>
+        <attribute name="accel">&lt;primary&gt;comma</attribute>
+      </item>
+      <item>
+        <attribute name="id">ide-editor-workspace-menu-shortcuts</attribute>
+        <attribute name="label" translatable="yes">Keyboard Shortcuts</attribute>
+        <attribute name="action">app.shortcuts</attribute>
+        <attribute name="accel">&lt;primary&gt;question</attribute>
+      </item>
+      <item>
+        <attribute name="id">ide-editor-workspace-menu-help</attribute>
+        <attribute name="label" translatable="yes">Help</attribute>
+        <attribute name="action">app.help</attribute>
+        <attribute name="accel">F1</attribute>
+      </item>
+      <item>
+        <attribute name="id">ide-editor-workspace-menu-about</attribute>
+        <attribute name="label" translatable="yes">About Builder</attribute>
+        <attribute name="action">app.about</attribute>
+      </item>
+    </section>
+    <section id="ide-editor-workspace-menu-quit-section">
+      <attribute name="id">ide-editor-workspace-menu-quit-section</attribute>
+      <item>
+        <attribute name="id">ide-editor-workspace-menu-quit</attribute>
+        <attribute name="label" translatable="yes">_Quit</attribute>
+        <attribute name="action">app.quit</attribute>
+      </item>
+    </section>
+  </menu>
+  <menu id="ide-editor-page-menu">
+    <section id="ide-editor-page-document-section">
+      <attribute name="label" translatable="yes">Document</attribute>
+    </section>
+    <section id="ide-editor-page-preview-section">
+      <attribute name="after">ide-editor-page-document-section</attribute>
+    </section>
+    <section id="ide-editor-page-reveal-section"/>
+    <section id="ide-editor-page-save-section">
+      <attribute name="after">ide-editor-page-reveal-section</attribute>
+      <item>
+        <attribute name="label" translatable="yes">Save</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">Save As</attribute>
+      </item>
+    </section>
+  </menu>
+</interface>
diff --git a/src/libide/editor/ide-editor-plugin-private.h b/src/libide/editor/ide-editor-init.c
similarity index 66%
rename from src/libide/editor/ide-editor-plugin-private.h
rename to src/libide/editor/ide-editor-init.c
index c86344740..53006395a 100644
--- a/src/libide/editor/ide-editor-plugin-private.h
+++ b/src/libide/editor/ide-editor-init.c
@@ -1,6 +1,6 @@
-/* ide-editor-plugin-private.h
+/* ide-editor-init.c
  *
- * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ * 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
@@ -18,10 +18,17 @@
  * SPDX-License-Identifier: GPL-3.0-or-later
  */
 
-#pragma once
+#define G_LOG_DOMAIN "ide-editor-init"
 
-#include <libide-core.h>
-#include <libpeas/peas.h>
+#include "config.h"
 
-IDE_AVAILABLE_IN_3_32
-void _ide_editor_register_types (PeasObjectModule *module);
+#include "ide-editor-page.h"
+#include "ide-editor-private.h"
+#include "ide-editor-workspace.h"
+
+void
+_ide_editor_init (void)
+{
+  g_type_ensure (IDE_TYPE_EDITOR_PAGE);
+  g_type_ensure (IDE_TYPE_EDITOR_WORKSPACE);
+}
diff --git a/src/libide/editor/ide-editor-page-actions.c b/src/libide/editor/ide-editor-page-actions.c
index 124e5daff..da4e04bf2 100644
--- a/src/libide/editor/ide-editor-page-actions.c
+++ b/src/libide/editor/ide-editor-page-actions.c
@@ -1,6 +1,6 @@
 /* ide-editor-page-actions.c
  *
- * Copyright 2017-2019 Christian Hergert <chergert redhat com>
+ * 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
@@ -23,195 +23,8 @@
 #include "config.h"
 
 #include <glib/gi18n.h>
-#include <libide-code.h>
-#include <libide-gui.h>
 
-#include "ide-editor-surface.h"
-#include "ide-editor-private.h"
-#include "ide-editor-print-operation.h"
-#include "ide-editor-settings-dialog.h"
-
-typedef struct
-{
-  IdeEditorPage *self;
-  guint line;
-  guint line_offset;
-} ReloadState;
-
-static void
-reload_state_free (ReloadState *state)
-{
-  g_clear_object (&state->self);
-  g_slice_free (ReloadState, state);
-}
-
-G_DEFINE_AUTOPTR_CLEANUP_FUNC (ReloadState, reload_state_free)
-
-static void
-ide_editor_page_actions_reload_cb (GObject      *object,
-                                   GAsyncResult *result,
-                                   gpointer      user_data)
-{
-  IdeBufferManager *bufmgr = (IdeBufferManager *)object;
-  g_autoptr(ReloadState) state = user_data;
-  g_autoptr(IdeBuffer) buffer = NULL;
-  g_autoptr(GError) error = NULL;
-
-  g_assert (IDE_IS_BUFFER_MANAGER (bufmgr));
-  g_assert (G_IS_ASYNC_RESULT (result));
-  g_assert (state != NULL);
-  g_assert (IDE_IS_EDITOR_PAGE (state->self));
-
-  if (state->self->progress_bar != NULL)
-    dzl_gtk_widget_hide_with_fade (GTK_WIDGET (state->self->progress_bar));
-
-  if (!(buffer = ide_buffer_manager_load_file_finish (bufmgr, result, &error)))
-    {
-      g_warning ("%s", error->message);
-      ide_page_report_error (IDE_PAGE (state->self),
-                             /* translators: %s is the error message */
-                             _("Failed to load file: %s"), error->message);
-      ide_page_set_failed (IDE_PAGE (state->self), TRUE);
-    }
-  else
-    {
-      IdeSourceView *view;
-      GtkTextIter iter;
-
-      view = ide_editor_page_get_view (state->self);
-      gtk_text_buffer_get_iter_at_line_offset (GTK_TEXT_BUFFER (buffer),
-                                               &iter,
-                                               state->line,
-                                               state->line_offset);
-      gtk_text_buffer_select_range (GTK_TEXT_BUFFER (buffer), &iter, &iter);
-      ide_source_view_scroll_to_iter (view,
-                                      &iter,
-                                      .25,
-                                      IDE_SOURCE_SCROLL_BOTH,
-                                      1.0,
-                                      0.5,
-                                      FALSE);
-
-    }
-
-  gtk_revealer_set_reveal_child (state->self->modified_revealer, FALSE);
-}
-
-static void
-ide_editor_page_actions_reload (GSimpleAction *action,
-                                GVariant      *param,
-                                gpointer       user_data)
-{
-  IdeEditorPage *self = user_data;
-  g_autoptr(IdeNotification) notif = NULL;
-  g_autoptr(IdeContext) context = NULL;
-  IdeBufferManager *bufmgr;
-  ReloadState *state;
-  GtkTextIter iter;
-  IdeBuffer *buffer;
-  GFile *file;
-
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-
-  buffer = ide_editor_page_get_buffer (self);
-  context = ide_buffer_ref_context (buffer);
-  bufmgr = ide_buffer_manager_from_context (context);
-  file = ide_buffer_get_file (buffer);
-
-  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (self->progress_bar), 0.0);
-  gtk_widget_show (GTK_WIDGET (self->progress_bar));
-
-  notif = ide_notification_new ();
-
-  ide_buffer_get_selection_bounds (buffer, &iter, NULL);
-
-  state = g_slice_new0 (ReloadState);
-  state->self = g_object_ref (self);
-  state->line = gtk_text_iter_get_line (&iter);
-  state->line_offset = gtk_text_iter_get_line_offset (&iter);
-
-  ide_buffer_manager_load_file_async (bufmgr,
-                                      file,
-                                      IDE_BUFFER_OPEN_FLAGS_FORCE_RELOAD,
-                                      notif,
-                                      NULL,
-                                      ide_editor_page_actions_reload_cb,
-                                      g_steal_pointer (&state));
-
-  g_object_bind_property (notif, "progress",
-                          self->progress_bar, "fraction",
-                          G_BINDING_SYNC_CREATE);
-}
-
-static void
-handle_print_result (IdeEditorPage           *self,
-                     GtkPrintOperation       *operation,
-                     GtkPrintOperationResult  result)
-{
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-  g_assert (GTK_IS_PRINT_OPERATION (operation));
-
-  if (result == GTK_PRINT_OPERATION_RESULT_ERROR)
-    {
-      g_autoptr(GError) error = NULL;
-
-      gtk_print_operation_get_error (operation, &error);
-
-      g_warning ("%s", error->message);
-      ide_page_report_error (IDE_PAGE (self),
-                             /* translators: %s is the error message */
-                             _("Print failed: %s"), error->message);
-    }
-}
-
-static void
-print_done (GtkPrintOperation       *operation,
-            GtkPrintOperationResult  result,
-            gpointer                 user_data)
-{
-  IdeEditorPage *self = user_data;
-
-  g_assert (GTK_IS_PRINT_OPERATION (operation));
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-
-  handle_print_result (self, operation, result);
-
-  g_object_unref (operation);
-  g_object_unref (self);
-}
-
-static void
-ide_editor_page_actions_print (GSimpleAction *action,
-                               GVariant      *param,
-                               gpointer       user_data)
-{
-  g_autoptr(IdeEditorPrintOperation) operation = NULL;
-  IdeEditorPage *self = user_data;
-  IdeSourceView *source_view;
-  GtkWidget *toplevel;
-  GtkPrintOperationResult result;
-
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-
-  toplevel = gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_TYPE_WINDOW);
-
-  source_view = ide_editor_page_get_view (self);
-  operation = ide_editor_print_operation_new (source_view);
-
-  /* keep a ref until "done" is emitted */
-  g_object_ref (operation);
-  g_signal_connect_after (g_object_ref (operation),
-                          "done",
-                          G_CALLBACK (print_done),
-                          g_object_ref (self));
-
-  result = gtk_print_operation_run (GTK_PRINT_OPERATION (operation),
-                                    GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
-                                    GTK_WINDOW (toplevel),
-                                    NULL);
-
-  handle_print_result (self, GTK_PRINT_OPERATION (operation), result);
-}
+#include "ide-editor-page-private.h"
 
 static void
 ide_editor_page_actions_save_cb (GObject      *object,
@@ -222,421 +35,51 @@ ide_editor_page_actions_save_cb (GObject      *object,
   g_autoptr(IdeEditorPage) self = user_data;
   g_autoptr(GError) error = NULL;
 
+  IDE_ENTRY;
+
   g_assert (IDE_IS_BUFFER (buffer));
   g_assert (G_IS_ASYNC_RESULT (result));
   g_assert (IDE_IS_EDITOR_PAGE (self));
 
   if (!ide_buffer_save_file_finish (buffer, result, &error))
-    {
-      g_warning ("%s", error->message);
-      ide_page_report_error (IDE_PAGE (self),
-                             /* translators: %s is the error message */
-                             _("Failed to save file: %s"), error->message);
-      ide_page_set_failed (IDE_PAGE (self), TRUE);
-    }
+    ide_page_report_error (IDE_PAGE (self),
+                           /* translators: %s is replaced with a technical error message */
+                           _("Failed to save file: %s"),
+                           error->message);
+
+  ide_page_set_progress (IDE_PAGE (self), NULL);
 
-  if (self->progress_bar != NULL)
-    dzl_gtk_widget_hide_with_fade (GTK_WIDGET (self->progress_bar));
+  IDE_EXIT;
 }
 
 static void
-ide_editor_page_actions_save (GSimpleAction *action,
-                              GVariant      *variant,
-                              gpointer       user_data)
+ide_editor_page_actions_save (GtkWidget  *widget,
+                              const char *action_name,
+                              GVariant   *param)
 {
-  IdeEditorPage *self = user_data;
+  IdeEditorPage *self = (IdeEditorPage *)widget;
   g_autoptr(IdeNotification) notif = NULL;
-  g_autoptr(IdeContext) context = NULL;
-  g_autoptr(GFile) local_file = NULL;
-  g_autoptr(GFile) workdir = NULL;
-  IdeBuffer *buffer;
-  GFile *file;
-
-  g_assert (G_IS_SIMPLE_ACTION (action));
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-
-  buffer = ide_editor_page_get_buffer (self);
-  g_return_if_fail (IDE_IS_BUFFER (buffer));
-
-  context = ide_buffer_ref_context (buffer);
-  g_return_if_fail (IDE_IS_CONTEXT (context));
-
-  file = ide_buffer_get_file (buffer);
-  g_return_if_fail (G_IS_FILE (file));
-
-  workdir = ide_context_ref_workdir (context);
-  g_assert (G_IS_FILE (workdir));
-
-  if (ide_buffer_get_is_temporary (buffer))
-    {
-      GtkFileChooserNative *dialog;
-      GtkWidget *toplevel;
-      gint ret;
-
-      toplevel = gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_TYPE_WINDOW);
-
-      dialog = gtk_file_chooser_native_new (_("Save File"),
-                                            GTK_WINDOW (toplevel),
-                                            GTK_FILE_CHOOSER_ACTION_SAVE,
-                                            _("Save"), _("Cancel"));
-
-      g_object_set (dialog,
-                    "do-overwrite-confirmation", TRUE,
-                    "local-only", FALSE,
-                    "modal", TRUE,
-                    "select-multiple", FALSE,
-                    "show-hidden", FALSE,
-                    NULL);
 
-      gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (dialog), workdir, NULL);
+  IDE_ENTRY;
 
-      ret = gtk_native_dialog_run (GTK_NATIVE_DIALOG (dialog));
-
-      if (ret == GTK_RESPONSE_ACCEPT)
-        file = local_file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
-
-      gtk_native_dialog_destroy (GTK_NATIVE_DIALOG (dialog));
-
-      if (local_file == NULL)
-        return;
-    }
+  g_assert (IDE_IS_EDITOR_PAGE (self));
 
-  ide_buffer_save_file_async (buffer,
-                              file,
+  ide_buffer_save_file_async (self->buffer,
+                              NULL,
                               NULL,
                               &notif,
                               ide_editor_page_actions_save_cb,
                               g_object_ref (self));
 
-  g_object_bind_property (notif, "progress", self->progress_bar, "fraction",
-                          G_BINDING_SYNC_CREATE);
-
-  gtk_widget_show (GTK_WIDGET (self->progress_bar));
-}
-
-
-static void
-ide_editor_page_actions_save_as_cb (GObject      *object,
-                                    GAsyncResult *result,
-                                    gpointer      user_data)
-{
-  IdeBuffer *buffer = (IdeBuffer *)object;
-  g_autoptr(IdeEditorPage) self = user_data;
-  g_autoptr(GError) error = NULL;
-
-  g_assert (IDE_IS_BUFFER (buffer));
-  g_assert (G_IS_ASYNC_RESULT (result));
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-
-  if (!ide_buffer_save_file_finish (buffer, result, &error))
-    {
-      /* In this case, the editor page hasn't failed since this is for an
-       * alternate file (which maybe we just don't have access to on the
-       * network or something).
-       *
-       * But we do still need to notify the user of the error.
-       */
-      g_warning ("%s", error->message);
-      ide_page_report_error (IDE_PAGE (self),
-                             /* translators: %s is the underlying error message */
-                             _("Failed to save file: %s"),
-                             error->message);
-    }
-
-  dzl_gtk_widget_hide_with_fade (GTK_WIDGET (self->progress_bar));
-}
-
-static void
-ide_editor_page_actions_save_as (GSimpleAction *action,
-                                 GVariant      *param,
-                                 gpointer       user_data)
-{
-  IdeEditorPage *self = user_data;
-  GtkFileChooserNative *dialog;
-  IdeBuffer *buffer;
-  GtkWidget *toplevel;
-  GFile *file;
-  gint ret;
-
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-
-  buffer = ide_editor_page_get_buffer (self);
-  file = ide_buffer_get_file (buffer);
-
-  /* Just redirect to the save flow if we have a temporary
-   * file currently. That way we can avoid splitting the
-   * flow to handle both cases here.
-   */
-  if (ide_buffer_get_is_temporary (buffer))
-    {
-      ide_editor_page_actions_save (action, NULL, user_data);
-      return;
-    }
-
-  toplevel = gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_TYPE_WINDOW);
-  dialog = gtk_file_chooser_native_new (_("Save a Copy"),
-                                        GTK_WINDOW (toplevel),
-                                        GTK_FILE_CHOOSER_ACTION_SAVE,
-                                        _("Save"),
-                                        _("Cancel"));
-
-  gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE);
-  gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (dialog), FALSE);
-  gtk_file_chooser_set_show_hidden (GTK_FILE_CHOOSER (dialog), FALSE);
-
-  if (file != NULL)
-    gtk_file_chooser_set_file (GTK_FILE_CHOOSER (dialog), file, NULL);
-
-  ret = gtk_native_dialog_run (GTK_NATIVE_DIALOG (dialog));
-
-  if (ret == GTK_RESPONSE_ACCEPT)
-    {
-      g_autoptr(GFile) save_as = NULL;
-      g_autoptr(IdeNotification) notif = NULL;
-
-      save_as = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
-
-      ide_buffer_save_file_async (buffer,
-                                  save_as,
-                                  NULL,
-                                  &notif,
-                                  ide_editor_page_actions_save_as_cb,
-                                  g_object_ref (self));
-
-      g_object_bind_property (notif, "progress", self->progress_bar, "fraction",
-                              G_BINDING_SYNC_CREATE);
-
-      gtk_widget_show (GTK_WIDGET (self->progress_bar));
-    }
-
-  gtk_native_dialog_destroy (GTK_NATIVE_DIALOG (dialog));
-}
-
-static void
-ide_editor_page_actions_find (GSimpleAction *action,
-                              GVariant      *variant,
-                              gpointer       user_data)
-{
-  IdeEditorPage *self = user_data;
-  GtkTextIter begin;
-  GtkTextIter end;
-
-  g_assert (G_IS_SIMPLE_ACTION (action));
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-
-  if (gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (self->buffer), &begin, &end))
-    {
-      g_autofree gchar *word = gtk_text_iter_get_slice (&begin, &end);
-      ide_editor_search_set_search_text (self->search, word);
-    }
-
-  ide_editor_search_bar_set_replace_mode (self->search_bar, FALSE);
-  gtk_revealer_set_reveal_child (self->search_revealer, TRUE);
-  gtk_widget_grab_focus (GTK_WIDGET (self->search_bar));
-}
-
-static void
-ide_editor_page_actions_find_replace (GSimpleAction *action,
-                                      GVariant      *variant,
-                                      gpointer       user_data)
-{
-  IdeEditorPage *self = user_data;
-  GtkTextIter begin;
-  GtkTextIter end;
-
-  g_assert (G_IS_SIMPLE_ACTION (action));
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-
-  if (gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (self->buffer), &begin, &end))
-    {
-      g_autofree gchar *word = gtk_text_iter_get_slice (&begin, &end);
-      ide_editor_search_set_search_text (self->search, word);
-    }
-
-  ide_editor_search_bar_set_replace_mode (self->search_bar, TRUE);
-  gtk_revealer_set_reveal_child (self->search_revealer, TRUE);
-  gtk_widget_grab_focus (GTK_WIDGET (self->search_bar));
-}
-
-static void
-ide_editor_page_actions_hide_search (GSimpleAction *action,
-                                     GVariant      *variant,
-                                     gpointer       user_data)
-{
-  IdeEditorPage *self = user_data;
-
-  g_assert (G_IS_SIMPLE_ACTION (action));
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-
-  gtk_revealer_set_reveal_child (self->search_revealer, FALSE);
-  gtk_widget_grab_focus (GTK_WIDGET (self->source_view));
-}
-
-static void
-ide_editor_page_actions_notify_file_settings (IdeEditorPage *self,
-                                              GParamSpec    *pspec,
-                                              IdeSourceView *source_view)
-{
-  IdeFileSettings *file_settings;
-  GActionGroup *group;
-
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-  g_assert (IDE_IS_SOURCE_VIEW (source_view));
-
-  group = gtk_widget_get_action_group (GTK_WIDGET (self), "file-settings");
-  g_assert (DZL_IS_PROPERTIES_GROUP (group));
-
-  file_settings = ide_source_view_get_file_settings (source_view);
-  g_assert (!file_settings || IDE_IS_FILE_SETTINGS (file_settings));
-
-  g_object_set (group, "object", file_settings, NULL);
-}
-
-static void
-ide_editor_page_actions_move_next_error (GSimpleAction *action,
-                                         GVariant      *variant,
-                                         gpointer       user_data)
-{
-  ide_editor_page_move_next_error (user_data);
-}
-
-static void
-ide_editor_page_actions_move_previous_error (GSimpleAction *action,
-                                             GVariant      *variant,
-                                             gpointer       user_data)
-{
-  ide_editor_page_move_previous_error (user_data);
-}
-
-static void
-ide_editor_page_actions_activate_next_search_result (GSimpleAction *action,
-                                                     GVariant      *variant,
-                                                     gpointer       user_data)
-{
-  IdeEditorPage *self = user_data;
-  GtkTextIter begin;
-  GtkTextIter end;
-
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-
-  ide_editor_page_move_next_search_result (self);
-
-  gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (self->buffer), &begin, &end);
-  gtk_widget_grab_focus (GTK_WIDGET (self->source_view));
-  gtk_text_buffer_select_range (GTK_TEXT_BUFFER (self->buffer), &begin, &end);
-  ide_source_view_scroll_to_insert (self->source_view);
-}
-
-static void
-ide_editor_page_actions_move_next_search_result (GSimpleAction *action,
-                                                 GVariant      *variant,
-                                                 gpointer       user_data)
-{
-  ide_editor_page_move_next_search_result (user_data);
-}
-
-static void
-ide_editor_page_actions_move_previous_search_result (GSimpleAction *action,
-                                                     GVariant      *variant,
-                                                     gpointer       user_data)
-{
-  ide_editor_page_move_previous_search_result (user_data);
-}
-
-static void
-ide_editor_page_actions_properties (GSimpleAction *action,
-                                    GVariant      *variant,
-                                    gpointer       user_data)
-{
-  IdeEditorPage *self = user_data;
-  IdeEditorSettingsDialog *dialog;
-
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-
-  dialog = ide_editor_settings_dialog_new (self);
-  g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL);
-  ide_gtk_window_present (GTK_WINDOW (dialog));
-}
-
-static void
-ide_editor_page_actions_toggle_map (GSimpleAction *action,
-                                    GVariant      *variant,
-                                    gpointer       user_data)
-{
-  IdeEditorPage *self = user_data;
-
-  g_assert (G_IS_SIMPLE_ACTION (action));
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-
-  ide_editor_page_set_show_map (self, !ide_editor_page_get_show_map (self));
-}
-
-static const GActionEntry editor_view_entries[] = {
-  { "activate-next-search-result", ide_editor_page_actions_activate_next_search_result },
-  { "find", ide_editor_page_actions_find },
-  { "find-replace", ide_editor_page_actions_find_replace },
-  { "hide-search", ide_editor_page_actions_hide_search },
-  { "move-next-error", ide_editor_page_actions_move_next_error },
-  { "move-next-search-result", ide_editor_page_actions_move_next_search_result },
-  { "move-previous-error", ide_editor_page_actions_move_previous_error },
-  { "move-previous-search-result", ide_editor_page_actions_move_previous_search_result },
-  { "properties", ide_editor_page_actions_properties },
-  { "print", ide_editor_page_actions_print },
-  { "reload", ide_editor_page_actions_reload },
-  { "save", ide_editor_page_actions_save },
-  { "save-as", ide_editor_page_actions_save_as },
-  { "toggle-map", ide_editor_page_actions_toggle_map },
-};
-
-void
-_ide_editor_page_init_actions (IdeEditorPage *self)
-{
-  g_autoptr(GSimpleActionGroup) group = NULL;
-  g_autoptr(DzlPropertiesGroup) sv_props = NULL;
-  g_autoptr(DzlPropertiesGroup) file_props = NULL;
-  IdeSourceView *source_view;
-
-  g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
-
-  source_view = ide_editor_page_get_view (self);
-
-  /* Setup our user-facing actions */
-  group = g_simple_action_group_new ();
-  g_action_map_add_action_entries (G_ACTION_MAP (group),
-                                   editor_view_entries,
-                                   G_N_ELEMENTS (editor_view_entries),
-                                   self);
-  gtk_widget_insert_action_group (GTK_WIDGET (self), "editor-page", G_ACTION_GROUP (group));
-
-  /* We want to access some settings properties as stateful GAction so they
-   * manipulated using regular Gtk widgets from the properties panel.
-   */
-  sv_props = dzl_properties_group_new (G_OBJECT (source_view));
-  dzl_properties_group_add_all_properties (sv_props);
-  dzl_properties_group_add_property_full (sv_props,
-                                          "use-spaces",
-                                          "insert-spaces-instead-of-tabs",
-                                          DZL_PROPERTIES_FLAGS_STATEFUL_BOOLEANS);
-  gtk_widget_insert_action_group (GTK_WIDGET (self), "source-view", G_ACTION_GROUP (sv_props));
+  ide_page_set_progress (IDE_PAGE (self), notif);
 
-  /*
-   * We want to bind our file-settings, used to tweak values in the
-   * source-view, to a GActionGroup that can be manipulated by the properties
-   * editor. Make sure we get notified of changes and sink the current state.
-   */
-  file_props = dzl_properties_group_new_for_type (IDE_TYPE_FILE_SETTINGS);
-  dzl_properties_group_add_all_properties (file_props);
-  g_signal_connect_swapped (source_view,
-                            "notify::file-settings",
-                            G_CALLBACK (ide_editor_page_actions_notify_file_settings),
-                            self);
-  gtk_widget_insert_action_group (GTK_WIDGET (self), "file-settings", G_ACTION_GROUP (file_props));
-  ide_editor_page_actions_notify_file_settings (self, NULL, source_view);
+  IDE_EXIT;
 }
 
 void
-_ide_editor_page_update_actions (IdeEditorPage *self)
+_ide_editor_page_class_actions_init (IdeEditorPageClass *klass)
 {
-  g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
 
+  gtk_widget_class_install_action (widget_class, "page.save", NULL, ide_editor_page_actions_save);
 }
diff --git a/src/libide/editor/ide-editor-page-addin.c b/src/libide/editor/ide-editor-page-addin.c
index 5bb1d8476..d90974935 100644
--- a/src/libide/editor/ide-editor-page-addin.c
+++ b/src/libide/editor/ide-editor-page-addin.c
@@ -1,6 +1,6 @@
 /* ide-editor-page-addin.c
  *
- * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ * Copyright 2015-2022 Christian Hergert <christian hergert me>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,8 +22,10 @@
 
 #include "config.h"
 
-#include "ide-editor-private.h"
+#include <libide-plugins.h>
+
 #include "ide-editor-page-addin.h"
+#include "ide-editor-page-private.h"
 
 G_DEFINE_INTERFACE (IdeEditorPageAddin, ide_editor_page_addin, G_TYPE_OBJECT)
 
@@ -88,8 +90,6 @@ ide_editor_page_addin_frame_set (IdeEditorPageAddin *self,
  * #IdeEditorPageAddinInterface, then %NULL is returned.
  *
  * Returns: (transfer none) (nullable): An #IdeEditorPageAddin or %NULL
- *
- * Since: 3.32
  */
 IdeEditorPageAddin *
 ide_editor_page_addin_find_by_module_name (IdeEditorPage *page,
diff --git a/src/libide/editor/ide-editor-page-addin.h b/src/libide/editor/ide-editor-page-addin.h
index 4b929b7aa..e0fd9ebfe 100644
--- a/src/libide/editor/ide-editor-page-addin.h
+++ b/src/libide/editor/ide-editor-page-addin.h
@@ -1,6 +1,6 @@
 /* ide-editor-page-addin.h
  *
- * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ * Copyright 2015-2022 Christian Hergert <christian hergert me>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -24,7 +24,6 @@
 # error "Only <libide-editor.h> can be included directly."
 #endif
 
-
 #include <libide-core.h>
 #include <libide-gui.h>
 
@@ -34,7 +33,7 @@ G_BEGIN_DECLS
 
 #define IDE_TYPE_EDITOR_PAGE_ADDIN (ide_editor_page_addin_get_type ())
 
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 G_DECLARE_INTERFACE (IdeEditorPageAddin, ide_editor_page_addin, IDE, EDITOR_PAGE_ADDIN, GObject)
 
 struct _IdeEditorPageAddinInterface
@@ -51,19 +50,19 @@ struct _IdeEditorPageAddinInterface
                               IdeFrame           *frame);
 };
 
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void                ide_editor_page_addin_load                (IdeEditorPageAddin *self,
                                                                IdeEditorPage      *page);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void                ide_editor_page_addin_unload              (IdeEditorPageAddin *self,
                                                                IdeEditorPage      *page);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void                ide_editor_page_addin_frame_set           (IdeEditorPageAddin *self,
                                                                IdeFrame           *frame);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void                ide_editor_page_addin_language_changed    (IdeEditorPageAddin *self,
                                                                const gchar        *language_id);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeEditorPageAddin *ide_editor_page_addin_find_by_module_name (IdeEditorPage      *page,
                                                                const gchar        *module_name);
 
diff --git a/src/libide/editor/ide-editor-page-private.h b/src/libide/editor/ide-editor-page-private.h
new file mode 100644
index 000000000..b9aca73b8
--- /dev/null
+++ b/src/libide/editor/ide-editor-page-private.h
@@ -0,0 +1,59 @@
+/* ide-editor-page-private.h
+ *
+ * Copyright 2017-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-plugins.h>
+
+#include "ide-editor-page.h"
+
+G_BEGIN_DECLS
+
+struct _IdeEditorPage
+{
+  IdePage                  parent_instance;
+
+  /* Owned references */
+  IdeExtensionSetAdapter  *addins;
+  IdeBuffer               *buffer;
+  IdeGutter               *gutter;
+
+  /* Settings Management */
+  IdeBindingGroup         *buffer_file_settings;
+  IdeBindingGroup         *view_file_settings;
+
+  /* Template widgets */
+  IdeSourceView           *view;
+  GtkScrolledWindow       *scroller;
+  GtkSourceMap            *map;
+  GtkRevealer             *map_revealer;
+
+  guint                    completion_blocked : 1;
+};
+
+void _ide_editor_page_class_actions_init         (IdeEditorPageClass *klass);
+void _ide_editor_page_settings_init              (IdeEditorPage      *self);
+void _ide_editor_page_settings_reload            (IdeEditorPage      *self);
+void _ide_editor_page_settings_connect_gutter    (IdeEditorPage      *self,
+                                                  IdeGutter          *gutter);
+void _ide_editor_page_settings_disconnect_gutter (IdeEditorPage      *self,
+                                                  IdeGutter          *gutter);
+
+G_END_DECLS
diff --git a/src/libide/editor/ide-editor-page-settings.c b/src/libide/editor/ide-editor-page-settings.c
index 0b4e72524..116c07482 100644
--- a/src/libide/editor/ide-editor-page-settings.c
+++ b/src/libide/editor/ide-editor-page-settings.c
@@ -1,6 +1,6 @@
 /* ide-editor-page-settings.c
  *
- * Copyright 2017-2019 Christian Hergert <chergert redhat com>
+ * 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
@@ -22,220 +22,261 @@
 
 #include "config.h"
 
-#include "ide-editor-private.h"
+#include "ide-editor-page-private.h"
 
-#include <gtksourceview/gtksource.h>
+static GSettings *editor_settings;
 
 static gboolean
-get_smart_home_end (GValue   *value,
-                    GVariant *variant,
-                    gpointer  user_data)
+indent_style_to_insert_spaces (GBinding     *binding,
+                               const GValue *from,
+                               GValue       *to,
+                               gpointer      user_data)
 {
-  if (g_variant_get_boolean (variant))
-    g_value_set_enum (value, GTK_SOURCE_SMART_HOME_END_BEFORE);
+  g_assert (G_IS_BINDING (binding));
+  g_assert (G_VALUE_HOLDS_ENUM (from));
+  g_assert (G_VALUE_HOLDS_BOOLEAN (to));
+  g_assert (user_data == NULL);
+
+  if (g_value_get_enum (from) == IDE_INDENT_STYLE_TABS)
+    g_value_set_boolean (to, FALSE);
   else
-    g_value_set_enum (value, GTK_SOURCE_SMART_HOME_END_DISABLED);
+    g_value_set_boolean (to, TRUE);
+
   return TRUE;
 }
 
-static gboolean
-get_wrap_mode (GValue   *value,
-               GVariant *variant,
-               gpointer  user_data)
+void
+_ide_editor_page_settings_reload (IdeEditorPage *self)
 {
-  const gchar *wrap_mode = g_variant_get_string (variant, NULL);
+  IdeFileSettings *file_settings;
 
-  if (!g_strcmp0 (wrap_mode, "always"))
-    g_value_set_enum (value, GTK_WRAP_WORD_CHAR);
-  else if (!g_strcmp0 (wrap_mode, "whitespace"))
-    g_value_set_enum (value, GTK_WRAP_WORD);
+  IDE_ENTRY;
+
+  g_return_if_fail (IDE_IS_MAIN_THREAD ());
+  g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
+  g_return_if_fail (IDE_IS_BUFFER (self->buffer));
+  g_return_if_fail (IDE_IS_SOURCE_VIEW (self->view));
+  g_return_if_fail (IDE_IS_BINDING_GROUP (self->buffer_file_settings));
+
+  file_settings = ide_buffer_get_file_settings (self->buffer);
+
+  ide_binding_group_set_source (self->buffer_file_settings, file_settings);
+  ide_binding_group_set_source (self->view_file_settings, file_settings);
+
+  IDE_EXIT;
+}
+
+static gboolean
+show_map_to_vscrollbar_policy (GValue   *value,
+                               GVariant *variant,
+                               gpointer  user_data)
+{
+  if (g_variant_get_boolean (variant))
+    g_value_set_enum (value, GTK_POLICY_EXTERNAL);
   else
-    g_value_set_enum (value, GTK_WRAP_NONE);
+    g_value_set_enum (value, GTK_POLICY_AUTOMATIC);
+
   return TRUE;
 }
 
-static void
-on_keybindings_changed (IdeEditorPage *self,
-                        const gchar   *key,
-                        GSettings     *settings)
+static gboolean
+grid_lines_to_background_pattern (GValue   *value,
+                                  GVariant *variant,
+                                  gpointer  user_data)
 {
-  IdeSourceView *source_view;
+  if (g_variant_get_boolean (variant))
+    g_value_set_enum (value, GTK_SOURCE_BACKGROUND_PATTERN_TYPE_GRID);
+  else
+    g_value_set_enum (value, GTK_SOURCE_BACKGROUND_PATTERN_TYPE_NONE);
 
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-  g_assert (g_strcmp0 (key, "keybindings") == 0);
-  g_assert (G_IS_SETTINGS (settings));
+  return TRUE;
+}
+
+static gboolean
+font_name_to_font_desc (GValue   *value,
+                        GVariant *variant,
+                        gpointer  user_data)
+{
+  const char *str;
 
-  source_view = ide_editor_page_get_view (self);
+  if ((str = g_variant_get_string (variant, NULL)))
+    g_value_take_boxed (value, pango_font_description_from_string (str));
+  else
+    g_value_set_boxed (value, NULL);
 
-  g_signal_emit_by_name (source_view,
-                         "set-mode",
-                         NULL,
-                         IDE_SOURCE_VIEW_MODE_TYPE_PERMANENT);
+  return TRUE;
 }
 
 static void
-on_draw_spaces_changed (IdeEditorPage *self,
-                        const gchar   *key,
-                        GSettings     *settings)
+notify_interactive_completion_cb (IdeEditorPage *self,
+                                  const char    *key,
+                                  GSettings     *settings)
 {
-  GtkSourceView *source_view;
-  GtkSourceSpaceDrawer *drawer;
-  guint flags;
-  GtkSourceSpaceLocationFlags location_flags = GTK_SOURCE_SPACE_LOCATION_NONE;
-  GtkSourceSpaceTypeFlags type_flags = GTK_SOURCE_SPACE_TYPE_NONE;
+  GtkSourceCompletion *completion;
 
   g_assert (IDE_IS_EDITOR_PAGE (self));
-  g_assert (g_strcmp0 (key, "draw-spaces") == 0);
   g_assert (G_IS_SETTINGS (settings));
 
-  source_view = GTK_SOURCE_VIEW (ide_editor_page_get_view (self));
-  drawer = gtk_source_view_get_space_drawer (source_view);
-  flags = g_settings_get_flags (settings, "draw-spaces");
+  completion = gtk_source_view_get_completion (GTK_SOURCE_VIEW (self->view));
 
-  if (flags == 0)
+  if (g_settings_get_boolean (settings, "interactive-completion"))
     {
-      gtk_source_space_drawer_set_enable_matrix (drawer, FALSE);
-      return;
+      if (self->completion_blocked)
+        {
+          self->completion_blocked = FALSE;
+          gtk_source_completion_unblock_interactive (completion);
+        }
     }
-
-  /* Reset the matrix before setting it */
-  gtk_source_space_drawer_set_types_for_locations (drawer, GTK_SOURCE_SPACE_LOCATION_ALL, 
GTK_SOURCE_SPACE_TYPE_NONE);
-
-  if (flags & 1)
-    type_flags |= GTK_SOURCE_SPACE_TYPE_SPACE;
-
-  if (flags & 2)
-    type_flags |= GTK_SOURCE_SPACE_TYPE_TAB;
-
-  if (flags & 4)
+  else
     {
-      gtk_source_space_drawer_set_types_for_locations (drawer, GTK_SOURCE_SPACE_LOCATION_ALL, 
GTK_SOURCE_SPACE_TYPE_NEWLINE);
-      type_flags |= GTK_SOURCE_SPACE_TYPE_NEWLINE;
+      if (!self->completion_blocked)
+        {
+          self->completion_blocked = TRUE;
+          gtk_source_completion_block_interactive (completion);
+        }
     }
-
-  if (flags & 8)
-    type_flags |= GTK_SOURCE_SPACE_TYPE_NBSP;
-
-  if (flags & 16)
-    location_flags |= GTK_SOURCE_SPACE_LOCATION_LEADING;
-
-  if (flags & 32)
-    location_flags |= GTK_SOURCE_SPACE_LOCATION_INSIDE_TEXT;
-
-  if (flags & 64)
-    location_flags |= GTK_SOURCE_SPACE_LOCATION_TRAILING;
-
-  if (type_flags > 0 && location_flags == 0)
-    location_flags |= GTK_SOURCE_SPACE_LOCATION_ALL;
-
-  gtk_source_space_drawer_set_enable_matrix (drawer, TRUE);
-  gtk_source_space_drawer_set_types_for_locations (drawer, location_flags, type_flags);
 }
 
 void
-_ide_editor_page_init_settings (IdeEditorPage *self)
+_ide_editor_page_settings_init (IdeEditorPage *self)
 {
-  IdeSourceView *source_view;
-  IdeBuffer *buffer;
-
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-  g_assert (self->editor_settings == NULL);
-  g_assert (self->insight_settings == NULL);
-
-  source_view = ide_editor_page_get_view (self);
-  buffer = ide_editor_page_get_buffer (self);
-
-  self->editor_settings = g_settings_new ("org.gnome.builder.editor");
-
-  g_settings_bind (self->editor_settings, "highlight-current-line",
-                   source_view, "highlight-current-line",
+  GtkSourceCompletion *completion;
+
+  IDE_ENTRY;
+
+  g_return_if_fail (IDE_IS_MAIN_THREAD ());
+  g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
+  g_return_if_fail (IDE_IS_SOURCE_VIEW (self->view));
+  g_return_if_fail (IDE_IS_BUFFER (self->buffer));
+  g_return_if_fail (self->buffer_file_settings == NULL);
+  g_return_if_fail (self->view_file_settings == NULL);
+
+  if (editor_settings == NULL)
+    editor_settings = g_settings_new ("org.gnome.builder.editor");
+
+  g_object_bind_property (IDE_APPLICATION_DEFAULT, "style-scheme",
+                          self->buffer, "style-scheme-name",
+                          G_BINDING_SYNC_CREATE);
+
+  self->buffer_file_settings = ide_binding_group_new ();
+  ide_binding_group_bind (self->buffer_file_settings,
+                          "insert-trailing-newline", self->buffer, "implicit-trailing-newline",
+                          G_BINDING_SYNC_CREATE);
+
+  self->view_file_settings = ide_binding_group_new ();
+  ide_binding_group_bind (self->view_file_settings,
+                          "auto-indent", self->view, "auto-indent",
+                          G_BINDING_SYNC_CREATE);
+  ide_binding_group_bind_full (self->view_file_settings,
+                               "indent-style", self->view, "insert-spaces-instead-of-tabs",
+                               G_BINDING_SYNC_CREATE,
+                               indent_style_to_insert_spaces, NULL, NULL, NULL);
+  ide_binding_group_bind (self->view_file_settings,
+                          "indent-width", self->view, "indent-width",
+                          G_BINDING_SYNC_CREATE);
+  ide_binding_group_bind (self->view_file_settings,
+                          "right-margin-position", self->view, "right-margin-position",
+                          G_BINDING_SYNC_CREATE);
+  ide_binding_group_bind (self->view_file_settings,
+                          "show-right-margin", self->view, "show-right-margin",
+                          G_BINDING_SYNC_CREATE);
+  ide_binding_group_bind (self->view_file_settings,
+                          "tab-width", self->view, "tab-width",
+                          G_BINDING_SYNC_CREATE);
+
+  g_settings_bind (editor_settings, "show-map",
+                   self->map_revealer, "reveal-child",
                    G_SETTINGS_BIND_GET);
-
-  g_settings_bind (self->editor_settings, "highlight-matching-brackets",
-                   buffer, "highlight-matching-brackets",
+  g_settings_bind (editor_settings, "highlight-current-line",
+                   self->view, "highlight-current-line",
                    G_SETTINGS_BIND_GET);
-
-  g_settings_bind (self->editor_settings, "show-line-changes",
-                   source_view, "show-line-changes",
-                   G_SETTINGS_BIND_GET);
-
-  g_settings_bind (self->editor_settings, "show-line-diagnostics",
-                   source_view, "show-line-diagnostics",
-                   G_SETTINGS_BIND_GET);
-
-  g_settings_bind (self->editor_settings, "show-line-numbers",
-                   source_view, "show-line-numbers",
-                   G_SETTINGS_BIND_GET);
-
-  g_settings_bind (self->editor_settings, "show-relative-line-numbers",
-                   source_view, "show-relative-line-numbers",
+  g_settings_bind_with_mapping (editor_settings, "show-map",
+                                self->scroller, "vscrollbar-policy",
+                                G_SETTINGS_BIND_GET,
+                                show_map_to_vscrollbar_policy,
+                                NULL, NULL, NULL);
+  g_settings_bind_with_mapping (editor_settings, "show-grid-lines",
+                                self->view, "background-pattern",
+                                G_SETTINGS_BIND_GET,
+                                grid_lines_to_background_pattern,
+                                NULL, NULL, NULL);
+  g_settings_bind (editor_settings, "enable-snippets",
+                   self->view, "enable-snippets",
                    G_SETTINGS_BIND_GET);
-
-  g_settings_bind (self->editor_settings, "smart-backspace",
-                   source_view, "smart-backspace",
+  g_settings_bind (editor_settings, "line-height",
+                   self->view, "line-height",
                    G_SETTINGS_BIND_GET);
 
-  g_settings_bind_with_mapping (self->editor_settings, "smart-home-end",
-                                source_view, "smart-home-end",
+  g_settings_bind_with_mapping (editor_settings, "font-name",
+                                self->view, "font-desc",
                                 G_SETTINGS_BIND_GET,
-                                get_smart_home_end, NULL, NULL, NULL);
+                                font_name_to_font_desc,
+                                NULL, NULL, NULL);
 
-  g_settings_bind (self->editor_settings, "style-scheme-name",
-                   buffer, "style-scheme-name",
+  completion = gtk_source_view_get_completion (GTK_SOURCE_VIEW (self->view));
+  g_settings_bind (editor_settings, "select-first-completion",
+                   completion, "select-on-show",
                    G_SETTINGS_BIND_GET);
 
-  g_settings_bind (self->editor_settings, "font-name",
-                   source_view, "font-name",
-                   G_SETTINGS_BIND_GET);
+#if 0
+  ide_binding_group_bind (self->view_file_settings,
+                          "insert-matching-brace", self->view, "insert-matching-brace",
+                          G_BINDING_SYNC_CREATE);
+  ide_binding_group_bind (self->view_file_settings,
+                          "overwrite-braces", self->view, "overwrite-braces",
+                          G_BINDING_SYNC_CREATE);
+#endif
+
+  g_signal_connect_object (editor_settings,
+                           "changed::interactive-completion",
+                           G_CALLBACK (notify_interactive_completion_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+  notify_interactive_completion_cb (self, NULL, editor_settings);
 
-  g_settings_bind (self->editor_settings, "overscroll",
-                   source_view, "overscroll",
-                   G_SETTINGS_BIND_GET);
+  _ide_editor_page_settings_reload (self);
 
-  g_settings_bind (self->editor_settings, "scroll-offset",
-                   source_view, "scroll-offset",
-                   G_SETTINGS_BIND_GET);
+  IDE_EXIT;
+}
 
-  g_settings_bind (self->editor_settings, "show-grid-lines",
-                   source_view, "show-grid-lines",
-                   G_SETTINGS_BIND_GET);
+void
+_ide_editor_page_settings_connect_gutter (IdeEditorPage *self,
+                                          IdeGutter     *gutter)
+{
+  IDE_ENTRY;
 
-  g_settings_bind_with_mapping (self->editor_settings, "wrap-text",
-                                source_view, "wrap-mode",
-                                G_SETTINGS_BIND_GET,
-                                get_wrap_mode, NULL, NULL, NULL);
+  g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
+  g_return_if_fail (IDE_IS_GUTTER (gutter));
 
-  g_settings_bind (self->editor_settings, "completion-n-rows",
-                   source_view, "completion-n-rows",
+  g_settings_bind (editor_settings, "show-line-numbers",
+                   gutter, "show-line-numbers",
                    G_SETTINGS_BIND_GET);
-
-  g_settings_bind (self->editor_settings, "interactive-completion",
-                   source_view, "interactive-completion",
+  g_settings_bind (editor_settings, "show-line-changes",
+                   gutter, "show-line-changes",
                    G_SETTINGS_BIND_GET);
-
-  g_settings_bind (self->editor_settings, "show-map",
-                   self, "show-map",
+  g_settings_bind (editor_settings, "show-relative-line-numbers",
+                   gutter, "show-relative-line-numbers",
                    G_SETTINGS_BIND_GET);
-
-  g_settings_bind (self->editor_settings, "auto-hide-map",
-                   self, "auto-hide-map",
+  g_settings_bind (editor_settings, "show-line-diagnostics",
+                   gutter, "show-line-diagnostics",
                    G_SETTINGS_BIND_GET);
 
-  g_signal_connect_object (self->editor_settings,
-                           "changed::keybindings",
-                           G_CALLBACK (on_keybindings_changed),
-                           self,
-                           G_CONNECT_SWAPPED);
+  IDE_EXIT;
+}
 
-  on_keybindings_changed (self, "keybindings", self->editor_settings);
+void
+_ide_editor_page_settings_disconnect_gutter (IdeEditorPage *self,
+                                             IdeGutter     *gutter)
+{
+  IDE_ENTRY;
 
-  g_signal_connect_object (self->editor_settings,
-                           "changed::draw-spaces",
-                           G_CALLBACK (on_draw_spaces_changed),
-                           self,
-                           G_CONNECT_SWAPPED);
+  g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
+  g_return_if_fail (IDE_IS_GUTTER (gutter));
 
-  on_draw_spaces_changed (self, "draw-spaces", self->editor_settings);
+  g_settings_unbind (gutter, "show-line-changes");
+  g_settings_unbind (gutter, "show-line-numbers");
+  g_settings_unbind (gutter, "show-relative-line-numbers");
+  g_settings_unbind (gutter, "show-line-diagnostics");
 
-  self->insight_settings = g_settings_new ("org.gnome.builder.code-insight");
+  IDE_EXIT;
 }
diff --git a/src/libide/editor/ide-editor-page.c b/src/libide/editor/ide-editor-page.c
index 38b315097..1483956e8 100644
--- a/src/libide/editor/ide-editor-page.c
+++ b/src/libide/editor/ide-editor-page.c
@@ -1,6 +1,6 @@
 /* ide-editor-page.c
  *
- * Copyright 2017-2019 Christian Hergert <chergert redhat com>
+ * Copyright 2017-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
@@ -22,366 +22,175 @@
 
 #include "config.h"
 
-#include <dazzle.h>
-#include <libpeas/peas.h>
-#include <gtksourceview/gtksource.h>
-#include <pango/pangofc-fontmap.h>
+#include <glib/gi18n.h>
 
-#include "ide-editor-page.h"
-#include "ide-editor-page-addin.h"
-#include "ide-editor-private.h"
-#include "ide-line-change-gutter-renderer.h"
+#include <libide-code.h>
+#include <libide-threading.h>
 
-#define AUTO_HIDE_TIMEOUT_SECONDS 5
+#include "ide-editor-page-addin.h"
+#include "ide-editor-page-private.h"
 
 enum {
   PROP_0,
-  PROP_AUTO_HIDE_MAP,
   PROP_BUFFER,
-  PROP_BUFFER_FILE,
-  PROP_SEARCH,
-  PROP_SHOW_MAP,
+  PROP_GUTTER,
   PROP_VIEW,
   N_PROPS
 };
 
-static void ide_editor_page_update_reveal_timer (IdeEditorPage *self);
-
-G_DEFINE_FINAL_TYPE (IdeEditorPage, ide_editor_page, IDE_TYPE_PAGE)
-
-DZL_DEFINE_COUNTER (instances, "Editor", "N Views", "Number of editor views");
+G_DEFINE_TYPE (IdeEditorPage, ide_editor_page, IDE_TYPE_PAGE)
 
 static GParamSpec *properties [N_PROPS];
-static FcConfig *localFontConfig;
-
-static void
-ide_editor_page_load_fonts (IdeEditorPage *self)
-{
-  PangoFontMap *font_map;
-  PangoFontDescription *font_desc;
-
-  if (g_once_init_enter (&localFontConfig))
-    {
-      const gchar *font_path = PACKAGE_DATADIR "/gnome-builder/fonts/BuilderBlocks.ttf";
-      FcConfig *config = FcInitLoadConfigAndFonts ();
-
-      if (g_getenv ("GB_IN_TREE_FONTS") != NULL)
-        font_path = "data/fonts/BuilderBlocks.ttf";
-
-      if (!g_file_test (font_path, G_FILE_TEST_IS_REGULAR))
-        g_warning ("Failed to locate \"%s\"", font_path);
-
-      FcConfigAppFontAddFile (config, (const FcChar8 *)font_path);
-
-      g_once_init_leave (&localFontConfig, config);
-    }
-
-  font_map = pango_cairo_font_map_new_for_font_type (CAIRO_FONT_TYPE_FT);
-  pango_fc_font_map_set_config (PANGO_FC_FONT_MAP (font_map), localFontConfig);
-  gtk_widget_set_font_map (GTK_WIDGET (self->map), font_map);
-  font_desc = pango_font_description_from_string ("BuilderBlocks");
-  pango_font_description_set_absolute_size (font_desc, (96.0/72.0) * 2 * PANGO_SCALE);
-
-  g_assert (localFontConfig != NULL);
-  g_assert (font_map != NULL);
-  g_assert (font_desc != NULL);
-
-  g_object_set (self->map, "font-desc", font_desc, NULL);
-
-  pango_font_description_free (font_desc);
-  g_object_unref (font_map);
-}
 
 static void
-ide_editor_page_update_icon (IdeEditorPage *self)
+ide_editor_page_query_file_info_cb (GObject      *object,
+                                    GAsyncResult *result,
+                                    gpointer      user_data)
 {
-  g_autofree gchar *name = NULL;
-  g_autofree gchar *content_type = NULL;
-  g_autofree gchar *sniff = NULL;
+  GFile *file = (GFile *)object;
+  g_autoptr(IdeEditorPage) self = user_data;
+  g_autoptr(GFileInfo) info = NULL;
   g_autoptr(GIcon) icon = NULL;
-  GtkTextIter begin, end;
-  GFile *file;
+  const char *content_type;
+  const char *name;
 
+  g_assert (G_IS_FILE (file));
+  g_assert (G_IS_ASYNC_RESULT (result));
   g_assert (IDE_IS_EDITOR_PAGE (self));
-  g_assert (IDE_IS_BUFFER (self->buffer));
-
-  /* Get first 1024 bytes to help determine content type */
-  gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (self->buffer), &begin, &end);
-  if (gtk_text_iter_get_offset (&end) > 1024)
-    gtk_text_iter_set_offset (&end, 1024);
-  sniff = gtk_text_iter_get_slice (&begin, &end);
 
-  /* Now get basename for content type */
-  file = ide_buffer_get_file (self->buffer);
-  name = g_file_get_basename (file);
-
-  /* Guess content type */
-  content_type = g_content_type_guess (name, (const guchar *)sniff, strlen (sniff), NULL);
+  if (!(info = g_file_query_info_finish (file, result, NULL)))
+    return;
 
-  /* Update icon to match guess */
+  content_type = g_file_info_get_content_type (info);
+  name = g_file_info_get_name (info);
   icon = ide_g_content_type_get_symbolic_icon (content_type, name);
-  ide_page_set_icon (IDE_PAGE (self), icon);
-}
-
-static void
-ide_editor_page_buffer_notify_failed (IdeEditorPage *self,
-                                      GParamSpec    *pspec,
-                                      IdeBuffer     *buffer)
-{
-  gboolean failed;
-
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-  g_assert (IDE_IS_BUFFER (buffer));
-
-  failed = ide_buffer_get_failed (buffer);
-
-  ide_page_set_failed (IDE_PAGE (self), failed);
-}
-
-static void
-ide_editor_page_stop_search (IdeEditorPage      *self,
-                             IdeEditorSearchBar *search_bar)
-{
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-  g_assert (IDE_IS_EDITOR_SEARCH_BAR (search_bar));
 
-  gtk_revealer_set_reveal_child (self->search_revealer, FALSE);
-  gtk_widget_grab_focus (GTK_WIDGET (self->source_view));
+  panel_widget_set_icon (PANEL_WIDGET (self), icon);
 }
 
 static void
-ide_editor_page_notify_child_revealed (IdeEditorPage *self,
-                                       GParamSpec    *pspec,
-                                       GtkRevealer   *revealer)
-{
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-  g_assert (GTK_IS_REVEALER (revealer));
-
-  if (gtk_revealer_get_child_revealed (revealer))
-    {
-      GtkWidget *toplevel = gtk_widget_get_ancestor (GTK_WIDGET (revealer), GTK_TYPE_WINDOW);
-      GtkWidget *focus = gtk_window_get_focus (GTK_WINDOW (toplevel));
-
-      /* Only focus the search bar if it doesn't already have focus,
-       * as it can reselect the search text.
-       */
-      if (focus == NULL || !gtk_widget_is_ancestor (focus, GTK_WIDGET (revealer)))
-        gtk_widget_grab_focus (GTK_WIDGET (self->search_bar));
-    }
-}
-
-static gboolean
-ide_editor_page_focus_in_event (IdeEditorPage *self,
-                                GdkEventFocus *focus,
-                                IdeSourceView *source_view)
+ide_editor_page_notify_file_cb (IdeEditorPage *self,
+                                GParamSpec    *pspec,
+                                IdeBuffer     *buffer)
 {
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-  g_assert (IDE_IS_SOURCE_VIEW (source_view));
-
-  ide_page_mark_used (IDE_PAGE (self));
-
-  return GDK_EVENT_PROPAGATE;
-}
+  GFile *file;
 
-static void
-ide_editor_page_buffer_loaded (IdeEditorPage *self,
-                               IdeBuffer     *buffer)
-{
   g_assert (IDE_IS_EDITOR_PAGE (self));
   g_assert (IDE_IS_BUFFER (buffer));
 
-  ide_editor_page_update_icon (self);
+  file = ide_buffer_get_file (buffer);
 
-  /* Scroll to the insertion location once the buffer
-   * has loaded. This is useful if it is not onscreen.
-   */
-  ide_source_view_scroll_to_insert (self->source_view);
+  g_file_query_info_async (file,
+                           G_FILE_ATTRIBUTE_STANDARD_NAME","
+                           G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
+                           G_FILE_QUERY_INFO_NONE,
+                           G_PRIORITY_DEFAULT,
+                           NULL,
+                           ide_editor_page_query_file_info_cb,
+                           g_object_ref (self));
 }
 
 static void
-ide_editor_page_buffer_modified_changed (IdeEditorPage *self,
-                                         IdeBuffer     *buffer)
+ide_editor_page_modified_changed_cb (IdeEditorPage *self,
+                                     IdeBuffer     *buffer)
 {
-  gboolean modified = FALSE;
+  IDE_ENTRY;
 
   g_assert (IDE_IS_EDITOR_PAGE (self));
   g_assert (IDE_IS_BUFFER (buffer));
 
-  if (!ide_buffer_get_loading (buffer))
-    modified = gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (buffer));
-
-  ide_page_set_modified (IDE_PAGE (self), modified);
-}
-
-static void
-ide_editor_page_buffer_notify_language_cb (IdeExtensionSetAdapter *set,
-                                           PeasPluginInfo         *plugin_info,
-                                           PeasExtension          *exten,
-                                           gpointer                user_data)
-{
-  const gchar *language_id = user_data;
-
-  g_assert (IDE_IS_EXTENSION_SET_ADAPTER (set));
-  g_assert (plugin_info != NULL);
-  g_assert (IDE_IS_EDITOR_PAGE_ADDIN (exten));
+  panel_widget_set_modified (PANEL_WIDGET (self),
+                             gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (buffer)));
 
-  ide_editor_page_addin_language_changed (IDE_EDITOR_PAGE_ADDIN (exten), language_id);
+  IDE_EXIT;
 }
 
 static void
-ide_editor_page_buffer_notify_language (IdeEditorPage *self,
-                                        GParamSpec    *pspec,
-                                        IdeBuffer     *buffer)
+ide_editor_page_style_scheme_changed_cb (IdeEditorPage *self,
+                                         GParamSpec    *pspec,
+                                         IdeBuffer     *buffer)
 {
-  const gchar *lang_id = NULL;
-
   g_assert (IDE_IS_EDITOR_PAGE (self));
   g_assert (IDE_IS_BUFFER (buffer));
 
-  if (self->addins == NULL)
-    return;
-
-  lang_id = ide_buffer_get_language_id (buffer);
-
-  /* Update extensions that change based on language */
-  ide_extension_set_adapter_set_value (self->addins, lang_id);
-  ide_extension_set_adapter_foreach (self->addins,
-                                     ide_editor_page_buffer_notify_language_cb,
-                                     (gpointer)lang_id);
-
-  ide_editor_page_update_icon (self);
+  if (self->gutter != NULL)
+    ide_gutter_style_changed (self->gutter);
 }
 
 static void
-ide_editor_page_buffer_notify_style_scheme (IdeEditorPage *self,
-                                            GParamSpec    *pspec,
-                                            IdeBuffer     *buffer)
+ide_editor_page_set_buffer (IdeEditorPage *self,
+                            IdeBuffer     *buffer)
 {
-  g_autofree gchar *background = NULL;
-  g_autofree gchar *foreground = NULL;
-  GtkSourceStyleScheme *scheme;
-  GtkSourceStyle *style;
-  gboolean background_set = FALSE;
-  gboolean foreground_set = FALSE;
-  GdkRGBA rgba;
-
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-  g_assert (IDE_IS_BUFFER (buffer));
-
-  if (NULL == (scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (buffer))) ||
-      NULL == (style = gtk_source_style_scheme_get_style (scheme, "text")))
-    goto unset_primary_color;
-
-  g_object_get (style,
-                "background-set", &background_set,
-                "background", &background,
-                "foreground-set", &foreground_set,
-                "foreground", &foreground,
-                NULL);
-
-  if (!background_set || background == NULL || !gdk_rgba_parse (&rgba, background))
-    goto unset_primary_color;
-
-  if (background_set && background != NULL && gdk_rgba_parse (&rgba, background))
-    ide_page_set_primary_color_bg (IDE_PAGE (self), &rgba);
-  else
-    goto unset_primary_color;
-
-  if (foreground_set && foreground != NULL && gdk_rgba_parse (&rgba, foreground))
-    ide_page_set_primary_color_fg (IDE_PAGE (self), &rgba);
-  else
-    ide_page_set_primary_color_fg (IDE_PAGE (self), NULL);
-
-  return;
-
-unset_primary_color:
-  ide_page_set_primary_color_bg (IDE_PAGE (self), NULL);
-  ide_page_set_primary_color_fg (IDE_PAGE (self), NULL);
-}
+  IDE_ENTRY;
 
-static void
-ide_editor_page__buffer_notify_changed_on_volume (IdeEditorPage *self,
-                                                  GParamSpec    *pspec,
-                                                  IdeBuffer     *buffer)
-{
   g_assert (IDE_IS_EDITOR_PAGE (self));
   g_assert (IDE_IS_BUFFER (buffer));
 
-  gtk_revealer_set_reveal_child (self->modified_revealer,
-                                 ide_buffer_get_changed_on_volume (buffer));
-}
-
-static void
-ide_editor_page_hide_reload_bar (IdeEditorPage *self,
-                                 GtkWidget     *button)
-{
-  g_assert (IDE_IS_EDITOR_PAGE (self));
+  if (g_set_object (&self->buffer, buffer))
+    {
+      ide_buffer_hold (buffer);
+
+      gtk_text_view_set_buffer (GTK_TEXT_VIEW (self->view), GTK_TEXT_BUFFER (buffer));
+
+      g_signal_connect_object (buffer,
+                               "modified-changed",
+                               G_CALLBACK (ide_editor_page_modified_changed_cb),
+                               self,
+                               G_CONNECT_SWAPPED);
+
+      g_signal_connect_object (buffer,
+                               "notify::file",
+                               G_CALLBACK (ide_editor_page_notify_file_cb),
+                               self,
+                               G_CONNECT_SWAPPED);
+
+      g_signal_connect_object (buffer,
+                               "notify::file-settings",
+                               G_CALLBACK (_ide_editor_page_settings_reload),
+                               self,
+                               G_CONNECT_SWAPPED);
+
+      g_signal_connect_object (buffer,
+                               "notify::style-scheme",
+                               G_CALLBACK (ide_editor_page_style_scheme_changed_cb),
+                               self,
+                               G_CONNECT_SWAPPED);
+
+      g_object_bind_property (buffer, "title",
+                              self, "title",
+                              G_BINDING_SYNC_CREATE);
+
+      ide_editor_page_notify_file_cb (self, NULL, buffer);
+      ide_editor_page_modified_changed_cb (self, buffer);
+      _ide_editor_page_settings_init (self);
+    }
 
-  gtk_revealer_set_reveal_child (self->modified_revealer, FALSE);
+  IDE_EXIT;
 }
 
 static gboolean
-ide_editor_page_source_view_event (IdeEditorPage *self,
-                                   GdkEvent      *event,
-                                   IdeSourceView *source_view)
+ide_editor_page_grab_focus (GtkWidget *widget)
 {
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-  g_assert (event != NULL);
-  g_assert (IDE_IS_SOURCE_VIEW (source_view) || GTK_SOURCE_IS_MAP (source_view));
-
-  if (self->auto_hide_map)
-    {
-      ide_editor_page_update_reveal_timer (self);
-      gtk_revealer_set_reveal_child (self->map_revealer, TRUE);
-    }
-
-  return GDK_EVENT_PROPAGATE;
+  return gtk_widget_grab_focus (GTK_WIDGET (IDE_EDITOR_PAGE (widget)->view));
 }
 
 static void
-ide_editor_page_bind_signals (IdeEditorPage  *self,
-                              IdeBuffer      *buffer,
-                              DzlSignalGroup *buffer_signals)
+ide_editor_page_focus_enter_cb (IdeEditorPage           *self,
+                                GtkEventControllerFocus *controller)
 {
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-  g_assert (IDE_IS_BUFFER (buffer));
-  g_assert (DZL_IS_SIGNAL_GROUP (buffer_signals));
+  g_autofree char *title = NULL;
 
-  ide_editor_page_buffer_modified_changed (self, buffer);
-  ide_editor_page_buffer_notify_language (self, NULL, buffer);
-  ide_editor_page_buffer_notify_style_scheme (self, NULL, buffer);
-  ide_editor_page_buffer_notify_failed (self, NULL, buffer);
-}
+  IDE_ENTRY;
 
-static void
-ide_editor_page_set_buffer (IdeEditorPage *self,
-                            IdeBuffer     *buffer)
-{
   g_assert (IDE_IS_EDITOR_PAGE (self));
-  g_assert (!buffer || IDE_IS_BUFFER (buffer));
-
-  if (g_set_object (&self->buffer, buffer))
-    {
-      dzl_signal_group_set_target (self->buffer_signals, buffer);
-      dzl_binding_group_set_source (self->buffer_bindings, buffer);
-      gtk_text_view_set_buffer (GTK_TEXT_VIEW (self->source_view),
-                                GTK_TEXT_BUFFER (buffer));
-      gtk_drag_dest_unset (GTK_WIDGET (self->source_view));
-      ide_editor_page_update_icon (self);
-    }
-}
+  g_assert (GTK_IS_EVENT_CONTROLLER_FOCUS (controller));
 
-static IdePage *
-ide_editor_page_create_split (IdePage *view)
-{
-  IdeEditorPage *self = (IdeEditorPage *)view;
+  title = ide_buffer_dup_title (self->buffer);
+  g_debug ("Keyboard focus entered page \"%s\"", title);
 
-  g_assert (IDE_IS_EDITOR_PAGE (self));
+  ide_page_mark_used (IDE_PAGE (self));
 
-  return g_object_new (IDE_TYPE_EDITOR_PAGE,
-                       "buffer", self->buffer,
-                       "visible", TRUE,
-                       NULL);
+  IDE_EXIT;
 }
 
 static void
@@ -416,18 +225,6 @@ ide_editor_page_addin_added (IdeExtensionSetAdapter *set,
   g_assert (IDE_IS_EDITOR_PAGE (self));
 
   ide_editor_page_addin_load (addin, self);
-
-  /*
-   * Notify of the current frame, but refetch the frame pointer just
-   * to be sure we aren't re-using an old pointer in case we're racing
-   * with a finalizer.
-   */
-  if (self->last_frame_ptr != NULL)
-    {
-      GtkWidget *frame = gtk_widget_get_ancestor (GTK_WIDGET (self), IDE_TYPE_FRAME);
-      if (frame != NULL)
-        ide_editor_page_addin_frame_set (addin, IDE_FRAME (frame));
-    }
 }
 
 static void
@@ -448,39 +245,26 @@ ide_editor_page_addin_removed (IdeExtensionSetAdapter *set,
 }
 
 static void
-ide_editor_page_hierarchy_changed (GtkWidget *widget,
-                                   GtkWidget *old_toplevel)
+ide_editor_page_root (GtkWidget *widget)
 {
   IdeEditorPage *self = (IdeEditorPage *)widget;
-  IdeFrame *frame;
   IdeContext *context;
+  GtkWidget *frame;
 
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-  g_assert (!old_toplevel || GTK_IS_WIDGET (old_toplevel));
+  IDE_ENTRY;
 
-  /*
-   * We don't need to chain up today, but if IdePage starts
-   * using the hierarchy_changed signal to handle anything, we want
-   * to make sure we aren't surprised.
-   */
-  if (GTK_WIDGET_CLASS (ide_editor_page_parent_class)->hierarchy_changed)
-    GTK_WIDGET_CLASS (ide_editor_page_parent_class)->hierarchy_changed (widget, old_toplevel);
+  GTK_WIDGET_CLASS (ide_editor_page_parent_class)->root (widget);
 
-  context = ide_widget_get_context (GTK_WIDGET (self));
-  frame = (IdeFrame *)gtk_widget_get_ancestor (widget, IDE_TYPE_FRAME);
+  context = ide_widget_get_context (widget);
+  frame = gtk_widget_get_ancestor (widget, IDE_TYPE_FRAME);
 
-  /*
-   * We don't want to create addins until the widget has been placed into
-   * the widget tree. That way the addins can get access to the context
-   * or other useful details.
-   */
-  if (context != NULL && self->addins == NULL)
+  if (self->addins == NULL && context != NULL)
     {
       self->addins = ide_extension_set_adapter_new (IDE_OBJECT (context),
                                                     peas_engine_get_default (),
                                                     IDE_TYPE_EDITOR_PAGE_ADDIN,
                                                     "Editor-Page-Languages",
-                                                    ide_editor_page_get_language_id (self));
+                                                    ide_buffer_get_language_id (self->buffer));
 
       g_signal_connect (self->addins,
                         "extension-added",
@@ -497,361 +281,65 @@ ide_editor_page_hierarchy_changed (GtkWidget *widget,
                                          self);
     }
 
-  /*
-   * If we have been moved into a new frame, notify the addins of the
-   * hierarchy change.
-   */
-  if (frame != NULL && frame != self->last_frame_ptr && self->addins != NULL)
-    {
-      self->last_frame_ptr = frame;
-      ide_extension_set_adapter_foreach (self->addins,
-                                         ide_editor_page_notify_frame_set,
-                                         frame);
-    }
-}
-
-static void
-ide_editor_page_update_map (IdeEditorPage *self)
-{
-  GtkWidget *parent;
-
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-
-  parent = gtk_widget_get_parent (GTK_WIDGET (self->map));
-
-  g_object_ref (self->map);
-
-  gtk_container_remove (GTK_CONTAINER (parent), GTK_WIDGET (self->map));
-
-  if (self->auto_hide_map)
-    gtk_container_add (GTK_CONTAINER (self->map_revealer), GTK_WIDGET (self->map));
-  else
-    gtk_container_add (GTK_CONTAINER (self->scroller_box), GTK_WIDGET (self->map));
-
-  gtk_widget_set_visible (GTK_WIDGET (self->map_revealer), self->show_map && self->auto_hide_map);
-  gtk_widget_set_visible (GTK_WIDGET (self->map), self->show_map);
-  gtk_revealer_set_reveal_child (self->map_revealer, self->show_map);
-
-  ide_editor_page_update_reveal_timer (self);
-
-  g_object_unref (self->map);
-}
-
-static void
-ide_editor_page_buffer_notify_file (IdeEditorPage *self,
-                                    GParamSpec    *pspec,
-                                    gpointer       user_data)
-{
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-
-
-}
-
-static void
-search_revealer_notify_reveal_child (IdeEditorPage *self,
-                                     GParamSpec    *pspec,
-                                     GtkRevealer   *revealer)
-{
-  IdeCompletion *completion;
-
-  g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
-  g_return_if_fail (pspec != NULL);
-  g_return_if_fail (GTK_IS_REVEALER (revealer));
-
-  completion = ide_source_view_get_completion (IDE_SOURCE_VIEW (self->source_view));
-
-  if (!gtk_revealer_get_reveal_child (revealer))
-    {
-      ide_editor_search_end_interactive (self->search);
-
-      /* Restore completion that we blocked below. */
-      ide_completion_unblock_interactive (completion);
-    }
-  else
-    {
-      ide_editor_search_begin_interactive (self->search);
-
-      /*
-       * Block the completion while the search bar is set. It only
-       * slows things down like replace functionality. We'll
-       * restore it above when we clear state.
-       */
-      ide_completion_block_interactive (completion);
-    }
-}
-
-static void
-ide_editor_page_focus_location (IdeEditorPage *self,
-                                IdeLocation   *location,
-                                IdeSourceView *source_view)
-{
-  GtkWidget *editor;
-
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-  g_assert (location != NULL);
-  g_assert (IDE_IS_SOURCE_VIEW (source_view));
+  if (self->addins != NULL && frame != NULL)
+    ide_extension_set_adapter_foreach (self->addins,
+                                       ide_editor_page_notify_frame_set,
+                                       frame);
 
-  editor = gtk_widget_get_ancestor (GTK_WIDGET (self), IDE_TYPE_EDITOR_SURFACE);
-  ide_editor_surface_focus_location (IDE_EDITOR_SURFACE (editor), location);
+  IDE_EXIT;
 }
 
-static void
-ide_editor_page_clear_search (IdeEditorPage *self,
-                              IdeSourceView *view)
+static IdePage *
+ide_editor_page_create_split (IdePage *page)
 {
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-  g_assert (IDE_IS_EDITOR_SEARCH (self->search));
-  g_assert (IDE_IS_SOURCE_VIEW (view));
-
-  ide_editor_search_set_search_text (self->search, NULL);
-  ide_editor_search_set_visible (self->search, FALSE);
-  gtk_revealer_set_reveal_child (self->search_revealer, FALSE);
-}
+  IdeEditorPage *self = (IdeEditorPage *)page;
+  GtkWidget *ret;
 
-static void
-ide_editor_page_move_search (IdeEditorPage    *self,
-                             GtkDirectionType  dir,
-                             gboolean          extend_selection,
-                             gboolean          select_match,
-                             gboolean          exclusive,
-                             gboolean          apply_count,
-                             gboolean          at_word_boundaries,
-                             IdeSourceView    *view)
-{
-  IdeEditorSearchSelect sel = 0;
+  IDE_ENTRY;
 
   g_assert (IDE_IS_EDITOR_PAGE (self));
-  g_assert (IDE_IS_EDITOR_SEARCH (self->search));
-  g_assert (IDE_IS_SOURCE_VIEW (view));
-
-  if (extend_selection && select_match)
-    sel = IDE_EDITOR_SEARCH_SELECT_WITH_RESULT;
-  else if (extend_selection)
-    sel = IDE_EDITOR_SEARCH_SELECT_TO_RESULT;
-
-  ide_editor_search_set_extend_selection (self->search, sel);
-  ide_editor_search_set_visible (self->search, TRUE);
-
-  if (apply_count)
-    {
-      ide_editor_search_set_repeat (self->search, ide_source_view_get_count (view));
-      g_signal_emit_by_name (view, "clear-count");
-    }
-
-  ide_editor_search_set_at_word_boundaries (self->search, at_word_boundaries);
-
-  switch (dir)
-    {
-    case GTK_DIR_DOWN:
-    case GTK_DIR_RIGHT:
-      ide_editor_search_set_reverse (self->search, FALSE);
-      ide_editor_search_move (self->search, IDE_EDITOR_SEARCH_NEXT);
-      break;
-
-    case GTK_DIR_TAB_FORWARD:
-      if (extend_selection)
-        ide_editor_search_move (self->search, IDE_EDITOR_SEARCH_FORWARD);
-      else
-        ide_editor_search_move (self->search, IDE_EDITOR_SEARCH_NEXT);
-      break;
 
-    case GTK_DIR_UP:
-    case GTK_DIR_LEFT:
-      ide_editor_search_set_reverse (self->search, TRUE);
-      ide_editor_search_move (self->search, IDE_EDITOR_SEARCH_NEXT);
-      break;
-
-    case GTK_DIR_TAB_BACKWARD:
-      if (extend_selection)
-        ide_editor_search_move (self->search, IDE_EDITOR_SEARCH_BACKWARD);
-      else
-        ide_editor_search_move (self->search, IDE_EDITOR_SEARCH_PREVIOUS);
-      break;
+  ret = ide_editor_page_new (self->buffer);
 
-    default:
-      break;
-    }
+  IDE_RETURN (IDE_PAGE (ret));
 }
 
-static void
-ide_editor_page_set_search_text (IdeEditorPage *self,
-                                 const gchar   *search_text,
-                                 gboolean       from_selection,
-                                 IdeSourceView *view)
+static GFile *
+ide_editor_page_get_file_or_directory (IdePage *page)
 {
-  g_autofree gchar *freeme = NULL;
-  GtkTextIter begin;
-  GtkTextIter end;
+  GFile *ret;
 
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-  g_assert (IDE_IS_EDITOR_SEARCH (self->search));
-  g_assert (search_text != NULL || from_selection);
-  g_assert (IDE_IS_SOURCE_VIEW (view));
+  IDE_ENTRY;
 
-  /* Use interactive mode if we're copying from the clipboard, because that
-   * is usually going to be followed by focusing the search box and we want
-   * to make sure the occurrance count is updated.
-   */
+  g_assert (IDE_IS_EDITOR_PAGE (page));
 
-  if (from_selection)
-    ide_editor_search_begin_interactive (self->search);
-
-  if (from_selection)
-    {
-      if (gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (self->buffer), &begin, &end))
-        search_text = freeme = gtk_text_iter_get_slice (&begin, &end);
-    }
+  ret = ide_buffer_get_file (IDE_EDITOR_PAGE (page)->buffer);
 
-  ide_editor_search_set_search_text (self->search, search_text);
-  ide_editor_search_set_regex_enabled (self->search, FALSE);
+  if (ret != NULL)
+    g_object_ref (ret);
 
-  if (from_selection)
-    ide_editor_search_end_interactive (self->search);
+  IDE_RETURN (ret);
 }
 
 static void
-ide_editor_page_constructed (GObject *object)
+ide_editor_page_dispose (GObject *object)
 {
   IdeEditorPage *self = (IdeEditorPage *)object;
-  GtkSourceGutterRenderer *renderer;
-  GtkSourceGutter *gutter;
-
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-
-  G_OBJECT_CLASS (ide_editor_page_parent_class)->constructed (object);
-
-  gutter = gtk_source_view_get_gutter (GTK_SOURCE_VIEW (self->map), GTK_TEXT_WINDOW_LEFT);
-  renderer = g_object_new (IDE_TYPE_LINE_CHANGE_GUTTER_RENDERER,
-                           "size", 1,
-                           "visible", TRUE,
-                           NULL);
-  gtk_source_gutter_insert (gutter, renderer, 0);
-
-  _ide_editor_page_init_actions (self);
-  _ide_editor_page_init_shortcuts (self);
-  _ide_editor_page_init_settings (self);
-
-  g_signal_connect_swapped (self->source_view,
-                            "focus-in-event",
-                            G_CALLBACK (ide_editor_page_focus_in_event),
-                            self);
-
-  g_signal_connect_swapped (self->source_view,
-                            "motion-notify-event",
-                            G_CALLBACK (ide_editor_page_source_view_event),
-                            self);
-
-  g_signal_connect_swapped (self->source_view,
-                            "scroll-event",
-                            G_CALLBACK (ide_editor_page_source_view_event),
-                            self);
-
-  g_signal_connect_swapped (self->source_view,
-                            "focus-location",
-                            G_CALLBACK (ide_editor_page_focus_location),
-                            self);
-
-  g_signal_connect_swapped (self->source_view,
-                            "set-search-text",
-                            G_CALLBACK (ide_editor_page_set_search_text),
-                            self);
-
-  g_signal_connect_swapped (self->source_view,
-                            "clear-search",
-                            G_CALLBACK (ide_editor_page_clear_search),
-                            self);
-
-  g_signal_connect_swapped (self->source_view,
-                            "move-search",
-                            G_CALLBACK (ide_editor_page_move_search),
-                            self);
-
-  g_signal_connect_swapped (self->map,
-                            "motion-notify-event",
-                            G_CALLBACK (ide_editor_page_source_view_event),
-                            self);
-
-
-
-  /*
-   * We want to track when the search revealer is visible. We will discard
-   * the search context when the revealer is not visible so that we don't
-   * continue performing expensive buffer operations.
-   */
-  g_signal_connect_swapped (self->search_revealer,
-                            "notify::reveal-child",
-                            G_CALLBACK (search_revealer_notify_reveal_child),
-                            self);
-
-  self->search = ide_editor_search_new (GTK_SOURCE_VIEW (self->source_view));
-  ide_editor_search_bar_set_search (self->search_bar, self->search);
-  gtk_widget_insert_action_group (GTK_WIDGET (self), "editor-search",
-                                  G_ACTION_GROUP (self->search));
-
-  ide_editor_page_load_fonts (self);
-  ide_editor_page_update_map (self);
-}
-
-static void
-ide_editor_page_destroy (GtkWidget *widget)
-{
-  IdeEditorPage *self = (IdeEditorPage *)widget;
 
-  g_assert (IDE_IS_EDITOR_PAGE (self));
-
-  /*
-   * WORKAROUND: We need to reset the drag dest to avoid warnings by Gtk
-   * reseting the target list for the source view.
-   */
-  if (self->source_view != NULL)
-    gtk_drag_dest_set (GTK_WIDGET (self->source_view),
-                       GTK_DEST_DEFAULT_ALL,
-                       NULL, 0, GDK_ACTION_COPY);
-
-  dzl_clear_source (&self->toggle_map_source);
+  ide_editor_page_set_gutter (self, NULL);
 
   ide_clear_and_destroy_object (&self->addins);
 
-  gtk_widget_insert_action_group (widget, "editor-search", NULL);
-  gtk_widget_insert_action_group (widget, "editor-page", NULL);
-
-  g_cancellable_cancel (self->destroy_cancellable);
-  g_clear_object (&self->destroy_cancellable);
-
-  g_clear_object (&self->search);
-  g_clear_object (&self->editor_settings);
-  g_clear_object (&self->insight_settings);
-
-  g_clear_object (&self->buffer);
+  g_clear_object (&self->buffer_file_settings);
+  g_clear_object (&self->view_file_settings);
 
-  if (self->buffer_bindings != NULL)
-    {
-      dzl_binding_group_set_source (self->buffer_bindings, NULL);
-      g_clear_object (&self->buffer_bindings);
-    }
-
-  if (self->buffer_signals != NULL)
+  if (self->buffer != NULL)
     {
-      dzl_signal_group_set_target (self->buffer_signals, NULL);
-      g_clear_object (&self->buffer_signals);
+      ide_buffer_release (self->buffer);
+      g_clear_object (&self->buffer);
     }
 
-  GTK_WIDGET_CLASS (ide_editor_page_parent_class)->destroy (widget);
-}
-
-static GFile *
-ide_editor_page_get_file_or_directory (IdePage *page)
-{
-  GFile *ret = ide_editor_page_get_file (IDE_EDITOR_PAGE (page));
-  return ret ? g_object_ref (ret) : NULL;
-}
-
-static void
-ide_editor_page_finalize (GObject *object)
-{
-  G_OBJECT_CLASS (ide_editor_page_parent_class)->finalize (object);
-
-  DZL_COUNTER_DEC (instances);
+  G_OBJECT_CLASS (ide_editor_page_parent_class)->dispose (object);
 }
 
 static void
@@ -864,29 +352,18 @@ ide_editor_page_get_property (GObject    *object,
 
   switch (prop_id)
     {
-    case PROP_AUTO_HIDE_MAP:
-      g_value_set_boolean (value, ide_editor_page_get_auto_hide_map (self));
-      break;
-
     case PROP_BUFFER:
       g_value_set_object (value, ide_editor_page_get_buffer (self));
       break;
 
-    case PROP_BUFFER_FILE:
-      g_value_set_object (value, ide_buffer_get_file (self->buffer));
+    case PROP_GUTTER:
+      g_value_set_object (value, ide_editor_page_get_gutter (self));
       break;
+
     case PROP_VIEW:
       g_value_set_object (value, ide_editor_page_get_view (self));
       break;
 
-    case PROP_SEARCH:
-      g_value_set_object (value, ide_editor_page_get_search (self));
-      break;
-
-    case PROP_SHOW_MAP:
-      g_value_set_boolean (value, ide_editor_page_get_show_map (self));
-      break;
-
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
@@ -902,16 +379,12 @@ ide_editor_page_set_property (GObject      *object,
 
   switch (prop_id)
     {
-    case PROP_AUTO_HIDE_MAP:
-      ide_editor_page_set_auto_hide_map (self, g_value_get_boolean (value));
-      break;
-
     case PROP_BUFFER:
       ide_editor_page_set_buffer (self, g_value_get_object (value));
       break;
 
-    case PROP_SHOW_MAP:
-      ide_editor_page_set_show_map (self, g_value_get_boolean (value));
+    case PROP_GUTTER:
+      ide_editor_page_set_gutter (self, g_value_get_object (value));
       break;
 
     default:
@@ -926,519 +399,463 @@ ide_editor_page_class_init (IdeEditorPageClass *klass)
   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
   IdePageClass *page_class = IDE_PAGE_CLASS (klass);
 
-  object_class->finalize = ide_editor_page_finalize;
-  object_class->constructed = ide_editor_page_constructed;
+  object_class->dispose = ide_editor_page_dispose;
   object_class->get_property = ide_editor_page_get_property;
   object_class->set_property = ide_editor_page_set_property;
 
-  widget_class->destroy = ide_editor_page_destroy;
-  widget_class->hierarchy_changed = ide_editor_page_hierarchy_changed;
+  widget_class->grab_focus = ide_editor_page_grab_focus;
+  widget_class->root = ide_editor_page_root;
 
-  page_class->create_split = ide_editor_page_create_split;
   page_class->get_file_or_directory = ide_editor_page_get_file_or_directory;
+  page_class->create_split = ide_editor_page_create_split;
 
+  /**
+   * IdeEditorPage:buffer:
+   *
+   * The #IdeBuffer that is displayed within the #IdeSourceView.
+   */
   properties [PROP_BUFFER] =
     g_param_spec_object ("buffer",
                          "Buffer",
-                         "The buffer for the view",
+                         "The buffer to be displayed within the page",
                          IDE_TYPE_BUFFER,
                          (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
 
-  /* It's really just there to get notify:: support for the buffer's file property
-   * but through the page, for the session addin.
+  /**
+   * IdeEditorPage:gutter:
+   *
+   * The "gutter" property contains an #IdeGutter or %NULL, which is a
+   * specialized renderer for the sourceview which can bring together a number
+   * of types of content which needs to be displayed, in a single renderer.
+   */
+  properties [PROP_GUTTER] =
+    g_param_spec_object ("gutter",
+                         "Gutter",
+                         "The primary gutter renderer in the left gutter window",
+                         IDE_TYPE_GUTTER,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * IdeEditorPage:view:
+   *
+   * The #IdeSourceView contained within the page.
    */
-  properties [PROP_BUFFER_FILE] =
-    g_param_spec_object ("buffer-file",
-                         "Buffer file",
-                         "The buffer file for the view's buffer",
-                         G_TYPE_FILE,
-                         (G_PARAM_STATIC_STRINGS | G_PARAM_READABLE));
-
-  properties [PROP_SEARCH] =
-    g_param_spec_object ("search",
-                         "Search",
-                         "An search helper for the document",
-                         IDE_TYPE_EDITOR_SEARCH,
-                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-
-  properties [PROP_SHOW_MAP] =
-    g_param_spec_boolean ("show-map",
-                          "Show Map",
-                          "If the overview map should be shown",
-                          FALSE,
-                          (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
-
-  properties [PROP_AUTO_HIDE_MAP] =
-    g_param_spec_boolean ("auto-hide-map",
-                          "Auto Hide Map",
-                          "If the overview map should be auto-hidden",
-                          FALSE,
-                          (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
-
   properties [PROP_VIEW] =
     g_param_spec_object ("view",
                          "View",
-                         "The view for editing the buffer",
+                         "The view displaying the buffer",
                          IDE_TYPE_SOURCE_VIEW,
                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
   g_object_class_install_properties (object_class, N_PROPS, properties);
 
-  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/libide-editor/ui/ide-editor-page.ui");
+  gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/libide-editor/ide-editor-page.ui");
   gtk_widget_class_bind_template_child (widget_class, IdeEditorPage, map);
   gtk_widget_class_bind_template_child (widget_class, IdeEditorPage, map_revealer);
-  gtk_widget_class_bind_template_child (widget_class, IdeEditorPage, overlay);
-  gtk_widget_class_bind_template_child (widget_class, IdeEditorPage, progress_bar);
   gtk_widget_class_bind_template_child (widget_class, IdeEditorPage, scroller);
-  gtk_widget_class_bind_template_child (widget_class, IdeEditorPage, scroller_box);
-  gtk_widget_class_bind_template_child (widget_class, IdeEditorPage, search_bar);
-  gtk_widget_class_bind_template_child (widget_class, IdeEditorPage, search_revealer);
-  gtk_widget_class_bind_template_child (widget_class, IdeEditorPage, modified_revealer);
-  gtk_widget_class_bind_template_child (widget_class, IdeEditorPage, modified_cancel_button);
-  gtk_widget_class_bind_template_child (widget_class, IdeEditorPage, source_view);
-  gtk_widget_class_bind_template_callback (widget_class, ide_editor_page_notify_child_revealed);
-  gtk_widget_class_bind_template_callback (widget_class, ide_editor_page_stop_search);
-
-  g_type_ensure (IDE_TYPE_SOURCE_VIEW);
-  g_type_ensure (IDE_TYPE_EDITOR_SEARCH_BAR);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorPage, view);
+  gtk_widget_class_bind_template_callback (widget_class, ide_editor_page_focus_enter_cb);
+
+  gtk_widget_class_add_binding_action (widget_class, GDK_KEY_s, GDK_CONTROL_MASK, "page.save", NULL);
+
+  _ide_editor_page_class_actions_init (klass);
 }
 
 static void
 ide_editor_page_init (IdeEditorPage *self)
 {
-  DZL_COUNTER_INC (instances);
+  GtkSourceGutterRenderer *renderer;
+  GtkSourceGutter *gutter;
+  GMenu *menu;
 
   gtk_widget_init_template (GTK_WIDGET (self));
 
   ide_page_set_can_split (IDE_PAGE (self), TRUE);
-  ide_page_set_menu_id (IDE_PAGE (self), "ide-editor-page-document-menu");
-
-  self->destroy_cancellable = g_cancellable_new ();
-
-  /* Setup signals to monitor on the buffer. */
-  self->buffer_signals = dzl_signal_group_new (IDE_TYPE_BUFFER);
-
-  dzl_signal_group_connect_swapped (self->buffer_signals,
-                                    "loaded",
-                                    G_CALLBACK (ide_editor_page_buffer_loaded),
-                                    self);
-
-  dzl_signal_group_connect_swapped (self->buffer_signals,
-                                    "modified-changed",
-                                    G_CALLBACK (ide_editor_page_buffer_modified_changed),
-                                    self);
-
-  dzl_signal_group_connect_swapped (self->buffer_signals,
-                                    "notify::file",
-                                    G_CALLBACK (ide_editor_page_buffer_notify_file),
-                                    self);
-
-  dzl_signal_group_connect_swapped (self->buffer_signals,
-                                    "notify::failed",
-                                    G_CALLBACK (ide_editor_page_buffer_notify_failed),
-                                    self);
-
-  dzl_signal_group_connect_swapped (self->buffer_signals,
-                                    "notify::language",
-                                    G_CALLBACK (ide_editor_page_buffer_notify_language),
-                                    self);
-
-  dzl_signal_group_connect_swapped (self->buffer_signals,
-                                    "notify::style-scheme",
-                                    G_CALLBACK (ide_editor_page_buffer_notify_style_scheme),
-                                    self);
-  dzl_signal_group_connect_swapped (self->buffer_signals,
-                                    "notify::changed-on-volume",
-                                    G_CALLBACK (ide_editor_page__buffer_notify_changed_on_volume),
-                                    self);
-
-  g_signal_connect_swapped (self->buffer_signals,
-                            "bind",
-                            G_CALLBACK (ide_editor_page_bind_signals),
-                            self);
-
-  g_signal_connect_object (self->modified_cancel_button,
-                           "clicked",
-                           G_CALLBACK (ide_editor_page_hide_reload_bar),
-                           self,
-                           G_CONNECT_SWAPPED);
-
-  /* Setup bindings for the buffer. */
-  self->buffer_bindings = dzl_binding_group_new ();
-  dzl_binding_group_bind (self->buffer_bindings, "title", self, "title", 0);
-
-  /* Load our custom font for the overview map. */
-  gtk_source_map_set_view (self->map, GTK_SOURCE_VIEW (self->source_view));
+  ide_page_set_menu_id (IDE_PAGE (self), "ide-editor-page-menu");
+
+  /* Add menus to source view */
+  menu = ide_application_get_menu_by_id (IDE_APPLICATION_DEFAULT, "ide-source-view-popup-menu");
+  ide_source_view_append_menu (self->view, G_MENU_MODEL (menu));
+
+  /* Add gutter changes to the overview map */
+  gutter = gtk_source_view_get_gutter (GTK_SOURCE_VIEW (self->map),
+                                       GTK_TEXT_WINDOW_LEFT);
+  renderer = g_object_new (IDE_TYPE_LINE_CHANGE_GUTTER_RENDERER,
+                           "width-request", 1,
+                           NULL);
+  gtk_source_gutter_insert (gutter, renderer, 100);
 }
 
-/**
- * ide_editor_page_get_buffer:
- * @self: a #IdeEditorPage
- *
- * Gets the underlying buffer for the view.
- *
- * Returns: (transfer none): An #IdeBuffer
- *
- * Since: 3.32
- */
-IdeBuffer *
-ide_editor_page_get_buffer (IdeEditorPage *self)
+GtkWidget *
+ide_editor_page_new (IdeBuffer *buffer)
 {
-  g_return_val_if_fail (IDE_IS_EDITOR_PAGE (self), NULL);
+  g_return_val_if_fail (IDE_IS_BUFFER (buffer), NULL);
 
-  return self->buffer;
+  return g_object_new (IDE_TYPE_EDITOR_PAGE,
+                       "buffer", buffer,
+                       NULL);
 }
 
 /**
  * ide_editor_page_get_view:
  * @self: a #IdeEditorPage
  *
- * Gets the #IdeSourceView that is part of the #IdeEditorPage.
- *
- * Returns: (transfer none): An #IdeSourceView
+ * Gets the #IdeSourceView for the page.
  *
- * Since: 3.32
+ * Returns: (transfer none): an #IdeSourceView
  */
 IdeSourceView *
 ide_editor_page_get_view (IdeEditorPage *self)
 {
   g_return_val_if_fail (IDE_IS_EDITOR_PAGE (self), NULL);
 
-  return self->source_view;
+  return self->view;
 }
 
 /**
- * ide_editor_page_get_language_id:
+ * ide_editor_page_get_buffer:
  * @self: a #IdeEditorPage
  *
- * This is a helper to get the language-id of the underlying buffer.
- *
- * Returns: (nullable): the language-id as a string, or %NULL
+ * Gets the #IdeBuffer for the page.
  *
- * Since: 3.32
+ * Returns: (transfer none): an #IdeBuffer
  */
-const gchar *
-ide_editor_page_get_language_id (IdeEditorPage *self)
+IdeBuffer *
+ide_editor_page_get_buffer (IdeEditorPage *self)
 {
   g_return_val_if_fail (IDE_IS_EDITOR_PAGE (self), NULL);
 
-  if (self->buffer != NULL)
-    {
-      GtkSourceLanguage *language;
-
-      language = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (self->buffer));
-
-      if (language != NULL)
-        return gtk_source_language_get_id (language);
-    }
-
-  return NULL;
+  return self->buffer;
 }
 
 /**
- * ide_editor_page_scroll_to_line:
+ * ide_editor_page_get_file:
  * @self: a #IdeEditorPage
- * @line: the line to scroll to
  *
- * This is a helper to quickly jump to a given line without all the frills. It
- * will also ensure focus on the editor view, so that refocusing the view
- * afterwards does not cause the view to restore the cursor to the previous
- * location.
+ * Gets the file for the document.
  *
- * This will move the insert cursor.
+ * This is a convenience function around ide_buffer_get_file().
  *
- * Lines start from 0.
- *
- * Since: 3.32
+ * Returns: (transfer none): a #GFile
  */
-void
-ide_editor_page_scroll_to_line (IdeEditorPage *self,
-                                guint          line)
+GFile *
+ide_editor_page_get_file (IdeEditorPage *self)
 {
-  ide_editor_page_scroll_to_line_offset (self, line, 0);
+  g_return_val_if_fail (IDE_IS_EDITOR_PAGE (self), NULL);
+
+  return ide_buffer_get_file (self->buffer);
 }
 
-/**
- * ide_editor_page_scroll_to_line_offset:
- * @self: a #IdeEditorPage
- * @line: the line to scroll to
- * @line_offset: the line offset
- *
- * Like ide_editor_page_scroll_to_line() but allows specifying the
- * line offset (column) to place the cursor on.
- *
- * This will move the insert cursor.
- *
- * Lines and offsets start from 0.
- *
- * If @line_offset is zero, the first non-space character of @line will be
- * used instead.
- *
- * Since: 3.32
- */
-void
-ide_editor_page_scroll_to_line_offset (IdeEditorPage *self,
-                                       guint          line,
-                                       guint          line_offset)
+static void
+ide_editor_page_save_cb (GObject      *object,
+                         GAsyncResult *result,
+                         gpointer      user_data)
 {
-  GtkTextIter iter;
+  IdeBuffer *buffer = (IdeBuffer *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  IdeEditorPage *self;
 
-  g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
-  g_return_if_fail (self->buffer != NULL);
-  g_return_if_fail (line <= G_MAXINT);
+  IDE_ENTRY;
 
-  gtk_widget_grab_focus (GTK_WIDGET (self->source_view));
+  g_assert (IDE_IS_BUFFER (buffer));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
 
-  gtk_text_buffer_get_iter_at_line_offset (GTK_TEXT_BUFFER (self->buffer), &iter,
-                                           line, line_offset);
+  self = ide_task_get_source_object (task);
 
-  if (line_offset == 0)
-    {
-      while (!gtk_text_iter_ends_line (&iter) &&
-             g_unichar_isspace (gtk_text_iter_get_char (&iter)))
-        {
-          if (!gtk_text_iter_forward_char (&iter))
-            break;
-        }
-    }
+  g_assert (IDE_IS_EDITOR_PAGE (self));
 
-  gtk_text_buffer_select_range (GTK_TEXT_BUFFER (self->buffer), &iter, &iter);
-  ide_source_view_scroll_to_insert (self->source_view);
-}
+  ide_page_set_progress (IDE_PAGE (self), NULL);
 
-gboolean
-ide_editor_page_get_auto_hide_map (IdeEditorPage *self)
-{
-  g_return_val_if_fail (IDE_IS_EDITOR_PAGE (self), FALSE);
+  if (!ide_buffer_save_file_finish (buffer, result, &error))
+    ide_task_return_error (task, g_steal_pointer (&error));
+  else
+    ide_task_return_boolean (task, TRUE);
 
-  return self->auto_hide_map;
+  IDE_EXIT;
 }
 
-static gboolean
-ide_editor_page_auto_hide_cb (gpointer user_data)
+static void
+ide_editor_page_save_response (GtkFileChooserNative *native,
+                               int                   response,
+                               IdeTask              *task)
 {
-  IdeEditorPage *self = user_data;
+  IdeEditorPage *self;
+  IdeBuffer *buffer;
 
-  g_assert (IDE_IS_EDITOR_PAGE (self));
+  IDE_ENTRY;
 
-  self->toggle_map_source = 0;
-  gtk_revealer_set_reveal_child (self->map_revealer, FALSE);
+  g_assert (GTK_IS_FILE_CHOOSER_NATIVE (native));
+  g_assert (IDE_IS_TASK (task));
 
-  return G_SOURCE_REMOVE;
-}
+  self = ide_task_get_source_object (task);
+  buffer = ide_task_get_task_data (task);
 
-static void
-ide_editor_page_update_reveal_timer (IdeEditorPage *self)
-{
   g_assert (IDE_IS_EDITOR_PAGE (self));
+  g_assert (IDE_IS_BUFFER (buffer));
 
-  dzl_clear_source (&self->toggle_map_source);
-
-  if (self->auto_hide_map && gtk_revealer_get_reveal_child (self->map_revealer))
+  if (response == GTK_RESPONSE_ACCEPT)
     {
-      self->toggle_map_source =
-        gdk_threads_add_timeout_seconds_full (G_PRIORITY_LOW,
-                                              AUTO_HIDE_TIMEOUT_SECONDS,
-                                              ide_editor_page_auto_hide_cb,
-                                              g_object_ref (self),
-                                              g_object_unref);
+      g_autoptr(GFile) file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (native));
+      g_autoptr(IdeNotification) notif = NULL;
+
+      ide_buffer_save_file_async (buffer,
+                                  file,
+                                  ide_task_get_cancellable (task),
+                                  &notif,
+                                  ide_editor_page_save_cb,
+                                  g_object_ref (task));
+
+      ide_page_set_progress (IDE_PAGE (self), notif);
     }
+
+  gtk_native_dialog_destroy (GTK_NATIVE_DIALOG (native));
+  g_object_unref (task);
+
+  IDE_EXIT;
 }
 
 void
-ide_editor_page_set_auto_hide_map (IdeEditorPage *self,
-                                   gboolean       auto_hide_map)
+ide_editor_page_save_async (IdeEditorPage       *self,
+                            GCancellable        *cancellable,
+                            GAsyncReadyCallback  callback,
+                            gpointer             user_data)
 {
+  g_autoptr(IdeTask) task = NULL;
+  g_autoptr(IdeNotification) notif = NULL;
+
+  IDE_ENTRY;
+
   g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_return_if_fail (IDE_IS_BUFFER (self->buffer));
 
-  auto_hide_map = !!auto_hide_map;
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, ide_editor_page_save_async);
+  ide_task_set_task_data (task, ide_buffer_hold (self->buffer), ide_buffer_release);
 
-  if (auto_hide_map != self->auto_hide_map)
+  if (ide_buffer_get_is_temporary (self->buffer))
     {
-      self->auto_hide_map = auto_hide_map;
-      ide_editor_page_update_map (self);
-      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_AUTO_HIDE_MAP]);
+      g_autoptr(GFile) workdir = NULL;
+      GtkFileChooserNative *dialog;
+      IdeWorkspace *workspace;
+      IdeContext *context;
+
+      workspace = ide_widget_get_workspace (GTK_WIDGET (self));
+      context = ide_workspace_get_context (workspace);
+      workdir = ide_context_ref_workdir (context);
+
+      dialog = gtk_file_chooser_native_new (_("Save File"),
+                                            GTK_WINDOW (workspace),
+                                            GTK_FILE_CHOOSER_ACTION_SAVE,
+                                            _("Save"), _("Cancel"));
+
+      g_object_set (dialog,
+                    "do-overwrite-confirmation", TRUE,
+                    "modal", TRUE,
+                    "select-multiple", FALSE,
+                    "show-hidden", FALSE,
+                    NULL);
+
+      gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), workdir, NULL);
+
+      g_signal_connect (dialog,
+                        "response",
+                        G_CALLBACK (ide_editor_page_save_response),
+                        g_object_ref (task));
+
+      gtk_native_dialog_show (GTK_NATIVE_DIALOG (dialog));
+
+      IDE_EXIT;
     }
+
+  ide_buffer_save_file_async (self->buffer,
+                              ide_buffer_get_file (self->buffer),
+                              cancellable,
+                              &notif,
+                              ide_editor_page_save_cb,
+                              g_steal_pointer (&task));
+
+  ide_page_set_progress (IDE_PAGE (self), notif);
+
+  IDE_EXIT;
 }
 
 gboolean
-ide_editor_page_get_show_map (IdeEditorPage *self)
+ide_editor_page_save_finish (IdeEditorPage  *self,
+                             GAsyncResult   *result,
+                             GError        **error)
 {
+  gboolean ret;
+
+  IDE_ENTRY;
+
   g_return_val_if_fail (IDE_IS_EDITOR_PAGE (self), FALSE);
+  g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
 
-  return self->show_map;
+  ret = ide_task_propagate_boolean (IDE_TASK (result), error);
+
+  IDE_RETURN (ret);
 }
 
-void
-ide_editor_page_set_show_map (IdeEditorPage *self,
-                              gboolean       show_map)
+static void
+ide_editor_page_discard_changes_cb (GObject      *object,
+                                    GAsyncResult *result,
+                                    gpointer      user_data)
 {
-  g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
+  IdeBufferManager *bufmgr = (IdeBufferManager *)object;
+  g_autoptr(IdeBuffer) buffer = NULL;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  IdeEditorPage *self;
 
-  show_map = !!show_map;
+  IDE_ENTRY;
 
-  if (show_map != self->show_map)
-    {
-      self->show_map = show_map;
-      g_object_set (self->scroller,
-                    "vscrollbar-policy", show_map ? GTK_POLICY_EXTERNAL : GTK_POLICY_AUTOMATIC,
-                    NULL);
-      ide_editor_page_update_map (self);
-      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_AUTO_HIDE_MAP]);
-    }
-}
+  g_assert (IDE_IS_BUFFER_MANAGER (bufmgr));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
 
-/**
- * ide_editor_page_set_language:
- * @self: a #IdeEditorPage
- *
- * This is a convenience function to set the language on the underlying
- * #IdeBuffer text buffer.
- *
- * Since: 3.32
- */
-void
-ide_editor_page_set_language (IdeEditorPage     *self,
-                              GtkSourceLanguage *language)
-{
-  g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
-  g_return_if_fail (!language || GTK_SOURCE_IS_LANGUAGE (language));
+  self = ide_task_get_source_object (task);
 
-  gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (self->buffer), language);
-}
+  g_assert (IDE_IS_EDITOR_PAGE (self));
 
-/**
- * ide_editor_page_get_language:
- * @self: a #IdeEditorPage
- *
- * Gets the #GtkSourceLanguage that is used by the underlying buffer.
- *
- * Returns: (transfer none) (nullable): a #GtkSourceLanguage or %NULL.
- *
- * Since: 3.32
- */
-GtkSourceLanguage *
-ide_editor_page_get_language (IdeEditorPage *self)
-{
-  g_return_val_if_fail (IDE_IS_EDITOR_PAGE (self), NULL);
+  ide_page_set_progress (IDE_PAGE (self), NULL);
+
+  if (!(buffer = ide_buffer_manager_load_file_finish (bufmgr, result, &error)))
+    ide_task_return_error (task, g_steal_pointer (&error));
+  else
+    ide_task_return_boolean (task, TRUE);
 
-  return gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (self->buffer));
+  g_assert (!buffer || IDE_IS_BUFFER (buffer));
+
+  IDE_EXIT;
 }
 
-/**
- * ide_editor_page_move_next_error:
- * @self: a #IdeEditorPage
- *
- * Moves to the next error, if any.
- *
- * If there is no error, the insertion cursor is not moved.
- *
- * Since: 3.32
- */
 void
-ide_editor_page_move_next_error (IdeEditorPage *self)
-{
+ide_editor_page_discard_changes_async (IdeEditorPage       *self,
+                                       GCancellable        *cancellable,
+                                       GAsyncReadyCallback  callback,
+                                       gpointer             user_data)
+{
+  g_autoptr(IdeTask) task = NULL;
+  g_autoptr(IdeNotification) notif = NULL;
+  IdeBufferManager *bufmgr;
+  IdeContext *context;
+
+  IDE_ENTRY;
+
   g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_return_if_fail (IDE_IS_BUFFER (self->buffer));
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, ide_editor_page_discard_changes_async);
+  ide_task_set_task_data (task, ide_buffer_hold (self->buffer), ide_buffer_release);
+
+  if (ide_buffer_get_is_temporary (self->buffer))
+    {
+      ide_task_return_boolean (task, TRUE);
+      IDE_EXIT;
+    }
+
+  context = ide_widget_get_context (GTK_WIDGET (self));
+  bufmgr = ide_buffer_manager_from_context (context);
+  notif = ide_notification_new ();
+  ide_page_set_progress (IDE_PAGE (self), notif);
 
-  g_signal_emit_by_name (self->source_view, "move-error", GTK_DIR_DOWN);
+  ide_buffer_manager_load_file_async (bufmgr,
+                                      ide_buffer_get_file (self->buffer),
+                                      IDE_BUFFER_OPEN_FLAGS_FORCE_RELOAD,
+                                      notif,
+                                      cancellable,
+                                      ide_editor_page_discard_changes_cb,
+                                      g_steal_pointer (&task));
+
+  IDE_EXIT;
 }
 
-/**
- * ide_editor_page_move_previous_error:
- * @self: a #IdeEditorPage
- *
- * Moves the insertion cursor to the previous error.
- *
- * If there is no error, the insertion cursor is not moved.
- *
- * Since: 3.32
- */
-void
-ide_editor_page_move_previous_error (IdeEditorPage *self)
+gboolean
+ide_editor_page_discard_changes_finish (IdeEditorPage  *self,
+                                        GAsyncResult   *result,
+                                        GError        **error)
 {
-  g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
+  gboolean ret;
 
-  g_signal_emit_by_name (self->source_view, "move-error", GTK_DIR_UP);
+  IDE_ENTRY;
+
+  g_return_val_if_fail (IDE_IS_EDITOR_PAGE (self), FALSE);
+  g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
+
+  ret = ide_task_propagate_boolean (IDE_TASK (result), error);
+
+  IDE_RETURN (ret);
 }
 
 /**
- * ide_editor_page_move_next_search_result:
+ * ide_editor_page_get_gutter:
  * @self: a #IdeEditorPage
  *
- * Moves the insertion cursor to the next search result.
+ * Gets the #IdeGutter displayed in the editor page.
  *
- * If there is no search result, the insertion cursor is not moved.
- *
- * Since: 3.32
+ * Returns: (transfer none) (nullable): an #IdeGutter or %NULL
  */
-void
-ide_editor_page_move_next_search_result (IdeEditorPage *self)
+IdeGutter *
+ide_editor_page_get_gutter (IdeEditorPage *self)
 {
-  g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
-  g_return_if_fail (self->destroy_cancellable != NULL);
-  g_return_if_fail (self->buffer != NULL);
+  g_return_val_if_fail (IDE_IS_EDITOR_PAGE (self), NULL);
 
-  ide_editor_search_move (self->search, IDE_EDITOR_SEARCH_NEXT);
+  return self->gutter;
 }
 
-/**
- * ide_editor_page_move_previous_search_result:
- * @self: a #IdeEditorPage
- *
- * Moves the insertion cursor to the previous search result.
- *
- * If there is no search result, the insertion cursor is not moved.
- *
- * Since: 3.32
- */
 void
-ide_editor_page_move_previous_search_result (IdeEditorPage *self)
+ide_editor_page_set_gutter (IdeEditorPage *self,
+                            IdeGutter     *gutter)
 {
+  GtkSourceGutter *container;
+
+  IDE_ENTRY;
+
   g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
-  g_return_if_fail (self->destroy_cancellable != NULL);
-  g_return_if_fail (self->buffer != NULL);
+  g_return_if_fail (!gutter || IDE_IS_GUTTER (gutter));
 
-  ide_editor_search_move (self->search, IDE_EDITOR_SEARCH_PREVIOUS);
-}
+  if (gutter == self->gutter)
+    IDE_EXIT;
 
-/**
- * ide_editor_page_get_search:
- * @self: a #IdeEditorPage
- *
- * Gets the #IdeEditorSearch used to search within the document.
- *
- * Returns: (transfer none): An #IdeEditorSearch
- *
- * Since: 3.32
- */
-IdeEditorSearch *
-ide_editor_page_get_search (IdeEditorPage *self)
-{
-  g_return_val_if_fail (IDE_IS_EDITOR_PAGE (self), NULL);
+  container = gtk_source_view_get_gutter (GTK_SOURCE_VIEW (self->view),
+                                          GTK_TEXT_WINDOW_LEFT);
+
+  if (self->gutter)
+    {
+      gtk_source_gutter_remove (container, GTK_SOURCE_GUTTER_RENDERER (self->gutter));
+      _ide_editor_page_settings_disconnect_gutter (self, self->gutter);
+      g_clear_object (&self->gutter);
+    }
 
-  return self->search;
+  if (gutter)
+    {
+      g_set_object (&self->gutter, gutter);
+      gtk_source_gutter_insert (container, GTK_SOURCE_GUTTER_RENDERER (self->gutter), 0);
+      _ide_editor_page_settings_connect_gutter (self, self->gutter);
+      ide_gutter_style_changed (self->gutter);
+    }
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_GUTTER]);
+
+  IDE_EXIT;
 }
 
-/**
- * ide_editor_page_get_file:
- * @self: a #IdeEditorPage
- *
- * Gets the #GFile that represents the current file. This may be a temporary
- * file, but a #GFile will still be used for the temporary file.
- *
- * Returns: (transfer none): a #GFile for the current buffer
- *
- * Since: 3.32
- */
-GFile *
-ide_editor_page_get_file (IdeEditorPage *self)
+void
+ide_editor_page_scroll_to_visual_position (IdeEditorPage *self,
+                                           guint          line,
+                                           guint          column)
 {
-  IdeBuffer *buffer;
-
-  g_return_val_if_fail (IDE_IS_EDITOR_PAGE (self), NULL);
+  GtkTextIter iter;
 
-  if ((buffer = ide_editor_page_get_buffer (self)))
-    return ide_buffer_get_file (buffer);
+  g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
 
-  return NULL;
+  ide_source_view_get_iter_at_visual_position (self->view, &iter, line, column);
+  gtk_text_buffer_select_range (GTK_TEXT_BUFFER (self->buffer), &iter, &iter);
+  gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW (self->view),
+                                      gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (self->buffer)));
 }
diff --git a/src/libide/editor/ide-editor-page.h b/src/libide/editor/ide-editor-page.h
index e47c9bfd9..b4ebf0c3d 100644
--- a/src/libide/editor/ide-editor-page.h
+++ b/src/libide/editor/ide-editor-page.h
@@ -1,6 +1,6 @@
 /* ide-editor-page.h
  *
- * Copyright 2017-2019 Christian Hergert <chergert redhat com>
+ * Copyright 2017-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
@@ -24,59 +24,52 @@
 # error "Only <libide-editor.h> can be included directly."
 #endif
 
-#include <libide-code.h>
 #include <libide-core.h>
+#include <libide-code.h>
 #include <libide-gui.h>
 #include <libide-sourceview.h>
 
-#include "ide-editor-search.h"
-
 G_BEGIN_DECLS
 
 #define IDE_TYPE_EDITOR_PAGE (ide_editor_page_get_type())
 
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 G_DECLARE_FINAL_TYPE (IdeEditorPage, ide_editor_page, IDE, EDITOR_PAGE, IdePage)
 
-IDE_AVAILABLE_IN_3_32
-GFile             *ide_editor_page_get_file                    (IdeEditorPage     *self);
-IDE_AVAILABLE_IN_3_32
-IdeBuffer         *ide_editor_page_get_buffer                  (IdeEditorPage     *self);
-IDE_AVAILABLE_IN_3_32
-IdeSourceView     *ide_editor_page_get_view                    (IdeEditorPage     *self);
-IDE_AVAILABLE_IN_3_32
-IdeEditorSearch   *ide_editor_page_get_search                  (IdeEditorPage     *self);
-IDE_AVAILABLE_IN_3_32
-const gchar       *ide_editor_page_get_language_id             (IdeEditorPage     *self);
-IDE_AVAILABLE_IN_3_32
-void               ide_editor_page_scroll_to_line              (IdeEditorPage     *self,
-                                                                guint              line);
-IDE_AVAILABLE_IN_3_32
-void               ide_editor_page_scroll_to_line_offset       (IdeEditorPage     *self,
-                                                                guint              line,
-                                                                guint              line_offset);
-IDE_AVAILABLE_IN_3_32
-gboolean           ide_editor_page_get_auto_hide_map           (IdeEditorPage     *self);
-IDE_AVAILABLE_IN_3_32
-void               ide_editor_page_set_auto_hide_map           (IdeEditorPage     *self,
-                                                                gboolean           auto_hide_map);
-IDE_AVAILABLE_IN_3_32
-gboolean           ide_editor_page_get_show_map                (IdeEditorPage     *self);
-IDE_AVAILABLE_IN_3_32
-void               ide_editor_page_set_show_map                (IdeEditorPage     *self,
-                                                                gboolean           show_map);
-IDE_AVAILABLE_IN_3_32
-GtkSourceLanguage *ide_editor_page_get_language                (IdeEditorPage     *self);
-IDE_AVAILABLE_IN_3_32
-void               ide_editor_page_set_language                (IdeEditorPage     *self,
-                                                                GtkSourceLanguage *language);
-IDE_AVAILABLE_IN_3_32
-void               ide_editor_page_move_next_error             (IdeEditorPage     *self);
-IDE_AVAILABLE_IN_3_32
-void               ide_editor_page_move_previous_error         (IdeEditorPage     *self);
-IDE_AVAILABLE_IN_3_32
-void               ide_editor_page_move_next_search_result     (IdeEditorPage     *self);
-IDE_AVAILABLE_IN_3_32
-void               ide_editor_page_move_previous_search_result (IdeEditorPage     *self);
+IDE_AVAILABLE_IN_ALL
+GtkWidget     *ide_editor_page_new                       (IdeBuffer            *buffer);
+IDE_AVAILABLE_IN_ALL
+IdeBuffer     *ide_editor_page_get_buffer                (IdeEditorPage        *self);
+IDE_AVAILABLE_IN_ALL
+IdeSourceView *ide_editor_page_get_view                  (IdeEditorPage        *self);
+IDE_AVAILABLE_IN_ALL
+GFile         *ide_editor_page_get_file                  (IdeEditorPage        *self);
+IDE_AVAILABLE_IN_ALL
+IdeGutter     *ide_editor_page_get_gutter                (IdeEditorPage        *self);
+IDE_AVAILABLE_IN_ALL
+void           ide_editor_page_set_gutter                (IdeEditorPage        *self,
+                                                          IdeGutter            *gutter);
+IDE_AVAILABLE_IN_ALL
+void           ide_editor_page_discard_changes_async     (IdeEditorPage        *self,
+                                                          GCancellable         *cancellable,
+                                                          GAsyncReadyCallback   callback,
+                                                          gpointer              user_data);
+IDE_AVAILABLE_IN_ALL
+gboolean       ide_editor_page_discard_changes_finish    (IdeEditorPage        *self,
+                                                          GAsyncResult         *result,
+                                                          GError              **error);
+IDE_AVAILABLE_IN_ALL
+void           ide_editor_page_save_async                (IdeEditorPage        *self,
+                                                          GCancellable         *cancellable,
+                                                          GAsyncReadyCallback   callback,
+                                                          gpointer              user_data);
+IDE_AVAILABLE_IN_ALL
+gboolean       ide_editor_page_save_finish               (IdeEditorPage        *self,
+                                                          GAsyncResult         *result,
+                                                          GError              **error);
+IDE_AVAILABLE_IN_ALL
+void           ide_editor_page_scroll_to_visual_position (IdeEditorPage        *self,
+                                                          guint                 line,
+                                                          guint                 column);
 
 G_END_DECLS
diff --git a/src/libide/editor/ide-editor-page.ui b/src/libide/editor/ide-editor-page.ui
index 7a4227383..96949e5ae 100644
--- a/src/libide/editor/ide-editor-page.ui
+++ b/src/libide/editor/ide-editor-page.ui
@@ -1,122 +1,38 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <template class="IdeEditorPage" parent="IdePage">
-    <child>
-      <object class="GtkOverlay" id="overlay">
-        <property name="visible">true</property>
-        <child type="overlay">
-          <object class="GtkRevealer" id="search_revealer">
-            <property name="width-request">525</property>
-            <property name="halign">end</property>
-            <property name="valign">start</property>
-            <property name="margin-right">12</property>
-            <property name="reveal-child">false</property>
-            <property name="visible">true</property>
-            <signal name="notify::child-revealed" handler="ide_editor_page_notify_child_revealed" 
swapped="true" object="IdeEditorPage"/>
-            <child>
-              <object class="IdeEditorSearchBar" id="search_bar">
-                <property name="visible">true</property>
-                <signal name="stop-search" handler="ide_editor_page_stop_search" swapped="true" 
object="IdeEditorPage"/>
-              </object>
-            </child>
-          </object>
-          <packing>
-            <property name="index">1</property>
-          </packing>
-        </child>
-        <child type="overlay">
-          <object class="GtkRevealer" id="modified_revealer">
-            <property name="halign">fill</property>
-            <property name="valign">start</property>
-            <property name="visible">true</property>
-            <property name="reveal-child">false</property>
+    <property name="can-maximize">true</property>
+    <child type="content">
+      <object class="GtkBox">
+        <property name="orientation">horizontal</property>
+        <property name="vexpand">true</property>
+        <child>
+          <object class="GtkScrolledWindow" id="scroller">
             <child>
-              <object class="GtkInfoBar">
-                <property name="visible">true</property>
-                <child internal-child="action_area">
-                  <object class="GtkButtonBox">
-                    <property name="spacing">6</property>
-                    <property name="layout_style">end</property>
-                    <child>
-                      <object class="GtkButton">
-                        <property name="action-name">editor-page.reload</property>
-                        <property name="label" translatable="yes">_Reload</property>
-                        <property name="visible">true</property>
-                        <property name="receives_default">true</property>
-                        <property name="use_underline">true</property>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkButton" id="modified_cancel_button">
-                        <property name="label" translatable="yes">_Cancel</property>
-                        <property name="visible">true</property>
-                        <property name="use_underline">true</property>
-                      </object>
-                    </child>
-                  </object>
-                </child>
-                <child internal-child="content_area">
-                  <object class="GtkBox">
-                    <property name="spacing">16</property>
-                    <child>
-                      <object class="GtkLabel" id="modified_label">
-                        <property name="hexpand">true</property>
-                        <property name="label" translatable="yes">Builder has discovered that this file has 
been modified externally. Would you like to reload the file?</property>
-                        <property name="visible">true</property>
-                        <property name="wrap">true</property>
-                        <property name="xalign">0</property>
-                      </object>
-                    </child>
+              <object class="IdeSourceView" id="view">
+                <property name="monospace">true</property>
+                <property name="hexpand">true</property>
+                <property name="vexpand">true</property>
+                <property name="show-line-numbers">false</property>
+                <property name="left-margin">0</property>
+                <child>
+                  <object class="GtkEventControllerFocus">
+                    <signal name="enter" handler="ide_editor_page_focus_enter_cb" swapped="true" 
object="IdeEditorPage"/>
                   </object>
                 </child>
               </object>
             </child>
           </object>
         </child>
-        <child type="overlay">
-          <object class="GtkProgressBar" id="progress_bar">
-            <property name="hexpand">true</property>
-            <property name="valign">start</property>
-            <style>
-              <class name="osd"/>
-            </style>
-          </object>
-        </child>
-        <child type="overlay">
+        <child>
           <object class="GtkRevealer" id="map_revealer">
-            <property name="halign">end</property>
             <property name="transition-type">slide-left</property>
-            <property name="vexpand">true</property>
-          </object>
-          <packing>
-            <property name="index">0</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkBox" id="scroller_box">
-            <property name="orientation">horizontal</property>
-            <property name="visible">true</property>
-            <child>
-              <object class="GtkScrolledWindow" id="scroller">
-                <property name="resize-mode">queue</property>
-                <property name="expand">true</property>
-                <property name="visible">true</property>
-                <child>
-                  <object class="IdeSourceView" id="source_view">
-                    <property name="auto-indent">true</property>
-                    <property name="show-line-changes">true</property>
-                    <property name="show-line-numbers">true</property>
-                    <property name="visible">true</property>
-                  </object>
-                </child>
-              </object>
-            </child>
+            <property name="transition-duration">300</property>
+            <property name="reveal-child">true</property>
             <child>
               <object class="GtkSourceMap" id="map">
-                <property name="visible">false</property>
-                <style>
-                  <class name="source-map"/>
-                </style>
+                <property name="left-margin">6</property>
+                <property name="view">view</property>
               </object>
             </child>
           </object>
diff --git a/src/libide/editor/ide-editor-private.h b/src/libide/editor/ide-editor-private.h
index d43d548bf..7acad3f48 100644
--- a/src/libide/editor/ide-editor-private.h
+++ b/src/libide/editor/ide-editor-private.h
@@ -1,6 +1,6 @@
 /* ide-editor-private.h
  *
- * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ * Copyright 2017-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
@@ -20,89 +20,10 @@
 
 #pragma once
 
-#include <libide-gui.h>
-#include <libide-plugins.h>
-#include <libide-sourceview.h>
-#include <libpeas/peas.h>
-
-#include "ide-editor-addin.h"
-#include "ide-editor-page.h"
-#include "ide-editor-search-bar.h"
-#include "ide-editor-search.h"
-#include "ide-editor-sidebar.h"
-#include "ide-editor-surface.h"
-#include "ide-session-private.h"
+#include <glib.h>
 
 G_BEGIN_DECLS
 
-struct _IdeEditorSurface
-{
-  IdeSurface           parent_instance;
-
-  PeasExtensionSet    *addins;
-
-  /* Template widgets */
-  IdeGrid             *grid;
-  GtkOverlay          *overlay;
-  GtkStack            *loading_stack;
-
-  /* State before entering focus mode */
-  guint                prefocus_had_left : 1;
-  guint                prefocus_had_bottom : 1;
-
-  guint                restore_panel : 1;
-
-  IdeSession          *session;
-};
-
-struct _IdeEditorPage
-{
-  IdePage                  parent_instance;
-
-  IdeExtensionSetAdapter  *addins;
-
-  GSettings               *editor_settings;
-  GSettings               *insight_settings;
-
-  IdeBuffer               *buffer;
-  DzlBindingGroup         *buffer_bindings;
-  DzlSignalGroup          *buffer_signals;
-
-  IdeEditorSearch         *search;
-
-  GCancellable            *destroy_cancellable;
-
-  GtkSourceMap            *map;
-  GtkRevealer             *map_revealer;
-  GtkOverlay              *overlay;
-  GtkProgressBar          *progress_bar;
-  IdeSourceView           *source_view;
-  GtkScrolledWindow       *scroller;
-  GtkBox                  *scroller_box;
-  IdeEditorSearchBar      *search_bar;
-  GtkRevealer             *search_revealer;
-  GtkRevealer             *modified_revealer;
-  GtkButton               *modified_cancel_button;
-
-  /* Raw pointer used to determine when frame changes */
-  IdeFrame                *last_frame_ptr;
-
-  guint                    toggle_map_source;
-
-  guint                    auto_hide_map : 1;
-  guint                    show_map : 1;
-};
-
-void _ide_editor_page_init_actions         (IdeEditorPage      *self);
-void _ide_editor_page_init_settings        (IdeEditorPage      *self);
-void _ide_editor_page_init_shortcuts       (IdeEditorPage      *self);
-void _ide_editor_page_update_actions       (IdeEditorPage      *self);
-void _ide_editor_search_bar_init_shortcuts (IdeEditorSearchBar *self);
-void _ide_editor_sidebar_set_open_pages    (IdeEditorSidebar   *self,
-                                            GListModel         *open_pages);
-void _ide_editor_surface_set_loading       (IdeEditorSurface   *self,
-                                            gboolean            loading);
-void _ide_editor_surface_init_actions      (IdeEditorSurface   *self);
-void _ide_editor_surface_init_shortcuts    (IdeEditorSurface   *self);
+void _ide_editor_init (void);
 
 G_END_DECLS
diff --git a/src/libide/editor/ide-editor-utils.c b/src/libide/editor/ide-editor-utils.c
new file mode 100644
index 000000000..a7281fba1
--- /dev/null
+++ b/src/libide/editor/ide-editor-utils.c
@@ -0,0 +1,310 @@
+/* ide-editor-utils.c
+ *
+ * Copyright 2020 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 "ide-editor-utils"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include <string.h>
+#include <math.h>
+
+#include <glib/gi18n.h>
+#include <gtksourceview/gtksource.h>
+
+#include "ide-editor-utils.h"
+
+static const struct {
+  GtkSourceNewlineType type;
+  const char *id;
+  const char *label;
+} line_endings[] = {
+  { GTK_SOURCE_NEWLINE_TYPE_LF, "unix", N_("Unix/Linux (LF)") },
+  { GTK_SOURCE_NEWLINE_TYPE_CR, "mac", N_("Mac OS Classic (CR)") },
+  { GTK_SOURCE_NEWLINE_TYPE_CR_LF, "windows", N_("Windows (CR+LF)") },
+};
+
+static int
+sort_by_name (gconstpointer a,
+              gconstpointer b)
+{
+  return g_strcmp0 (gtk_source_encoding_get_name (a),
+                    gtk_source_encoding_get_name (b));
+}
+
+/**
+ * ide_editor_encoding_menu_new:
+ * @action_name: the action to activate when selecting menu items
+ *
+ * Creates a new #GMenuModel with items which will activate using
+ * their encoding charset for the action @action_name target.
+ *
+ * Returns: (transfer full): a #GMenuModel
+ */
+GMenuModel *
+ide_editor_encoding_menu_new (const char *action_name)
+{
+  g_autoptr(GMenu) menu = NULL;
+  GHashTable *submenus;
+  GMenu *top_menu;
+  GSList *all;
+
+  g_return_val_if_fail (action_name, NULL);
+
+  top_menu = g_menu_new ();
+
+  submenus = g_hash_table_new (g_str_hash, g_str_equal);
+  all = g_slist_sort (gtk_source_encoding_get_all (), sort_by_name);
+
+  /* Always place UTF8 at the top in it's own section */
+  {
+    g_autoptr(GMenu) section = g_menu_new ();
+    g_autoptr(GMenuItem) item = g_menu_item_new ("UTF-8", NULL);
+
+    g_menu_item_set_action_and_target (item, action_name, "s", "UTF-8");
+    g_menu_item_set_attribute (item, "role", "s", "check");
+    g_menu_append_item (section, item);
+    g_menu_append_section (top_menu, NULL, G_MENU_MODEL (section));
+  }
+
+  menu = g_menu_new ();
+  g_menu_append_section (top_menu, NULL, G_MENU_MODEL (menu));
+
+  for (const GSList *l = all; l; l = l->next)
+    {
+      GtkSourceEncoding *encoding = l->data;
+      const char *name = gtk_source_encoding_get_name (encoding);
+      const char *charset = gtk_source_encoding_get_charset (encoding);
+      g_autofree char *title = g_strdup_printf ("%s (%s)", name, charset);
+      g_autoptr(GMenuItem) item = g_menu_item_new (title, NULL);
+      GMenu *submenu;
+
+      if (name == NULL || charset == NULL)
+        continue;
+
+      if (!(submenu = g_hash_table_lookup (submenus, name)))
+        {
+          submenu = g_menu_new ();
+          g_menu_append_submenu (menu, name, G_MENU_MODEL (submenu));
+          g_hash_table_insert (submenus, (char *)name, submenu);
+          g_object_unref (submenu);
+        }
+
+      g_menu_item_set_action_and_target (item, action_name, "s", gtk_source_encoding_get_charset (encoding));
+      g_menu_item_set_attribute (item, "role", "s", "check");
+      g_menu_append_item (submenu, item);
+    }
+
+  g_hash_table_unref (submenus);
+  g_slist_free (all);
+
+  return G_MENU_MODEL (top_menu);
+}
+
+void
+ide_editor_file_chooser_add_encodings (GtkFileChooser *chooser)
+{
+  GPtrArray *choices;
+  GPtrArray *labels;
+  GSList *all;
+
+  g_return_if_fail (GTK_IS_FILE_CHOOSER (chooser));
+
+  all = g_slist_sort (gtk_source_encoding_get_all (), sort_by_name);
+  choices = g_ptr_array_new ();
+  labels = g_ptr_array_new_with_free_func (g_free);
+
+#define ADD_ENCODING(id, name)             \
+  G_STMT_START {                           \
+    g_ptr_array_add(choices, (char *)id);  \
+    g_ptr_array_add(labels, (char *)name); \
+  } G_STMT_END
+
+  ADD_ENCODING ("auto", g_strdup (N_("Automatically Detected")));
+
+  for (const GSList *l = all; l; l = l->next)
+    {
+      GtkSourceEncoding *encoding = l->data;
+      char *title = g_strdup_printf ("%s (%s)",
+                                     gtk_source_encoding_get_name (encoding),
+                                     gtk_source_encoding_get_charset (encoding));
+      ADD_ENCODING (gtk_source_encoding_get_charset (encoding), title);
+    }
+
+  ADD_ENCODING (NULL, NULL);
+#undef ADD_ENCODING
+
+  gtk_file_chooser_add_choice (chooser,
+                               "encoding",
+                               _("Character Encoding:"),
+                               (const char **)(gpointer)choices->pdata,
+                               (const char **)(gpointer)labels->pdata);
+  gtk_file_chooser_set_choice (chooser, "encoding", "auto");
+
+  g_slist_free (all);
+  g_clear_pointer (&choices, g_ptr_array_unref);
+  g_clear_pointer (&labels, g_ptr_array_unref);
+}
+
+void
+ide_editor_file_chooser_add_line_endings (GtkFileChooser       *chooser,
+                                          GtkSourceNewlineType  selected)
+{
+  static GArray *choices;
+  static GArray *labels;
+
+  g_return_if_fail (GTK_IS_FILE_CHOOSER (chooser));
+
+  if (choices == NULL)
+    {
+      choices = g_array_new (TRUE, FALSE, sizeof (char *));
+      labels = g_array_new (TRUE, FALSE, sizeof (char *));
+
+      for (guint i = 0; i < G_N_ELEMENTS (line_endings); i++)
+        {
+          const char *msg = g_dgettext (GETTEXT_PACKAGE, line_endings[i].label);
+
+          g_array_append_val (choices, line_endings[i].id);
+          g_array_append_val (labels, msg);
+        }
+    }
+
+  gtk_file_chooser_add_choice (chooser,
+                               "line-ending",
+                               _("Line Ending:"),
+                               (const char **)(gpointer)choices->data,
+                               (const char **)(gpointer)labels->data);
+  gtk_file_chooser_set_choice (chooser, "line-endings", "unix");
+
+  for (guint i = 0; i < G_N_ELEMENTS (line_endings); i++)
+    {
+      if (line_endings[i].type == selected)
+        {
+          gtk_file_chooser_set_choice (chooser, "line-endings", line_endings[i].id);
+          break;
+        }
+    }
+}
+
+const GtkSourceEncoding *
+ide_editor_file_chooser_get_encoding (GtkFileChooser *chooser)
+{
+  const char *encoding;
+
+  g_return_val_if_fail (GTK_IS_FILE_CHOOSER (chooser), NULL);
+
+  if ((encoding = gtk_file_chooser_get_choice (chooser, "encoding")))
+    {
+      if (strcmp (encoding, "auto") != 0)
+        return gtk_source_encoding_get_from_charset (encoding);
+    }
+
+  return NULL;
+}
+
+GtkSourceNewlineType
+ide_editor_file_chooser_get_line_ending (GtkFileChooser *chooser)
+{
+  const char *ending;
+
+  g_return_val_if_fail (GTK_IS_FILE_CHOOSER (chooser), 0);
+
+  if ((ending = gtk_file_chooser_get_choice (chooser, "line-ending")))
+    {
+      for (guint i = 0; i < G_N_ELEMENTS (line_endings); i++)
+        {
+          if (g_strcmp0 (ending, line_endings[i].id) == 0)
+            return line_endings[i].type;
+        }
+    }
+
+  return GTK_SOURCE_NEWLINE_TYPE_LF;
+}
+
+/**
+ * ide_editor_syntax_menu_new:
+ * @action_name: the action to activate when selecting menu items
+ *
+ * Creates a new #GMenuModel with items which will activate using
+ * their syntax id for the action @action_name target.
+ *
+ * Returns: (transfer full): a #GMenuModel
+ */
+GMenuModel *
+ide_editor_syntax_menu_new (const char *action_name)
+{
+  GtkSourceLanguageManager *manager;
+  const char * const *language_ids;
+  g_autofree char **sections = NULL;
+  GHashTable *submenus;
+  g_autoptr(GMenu) top_section = NULL;
+  GMenu *top_menu;
+  guint len = 0;
+
+  g_return_val_if_fail (action_name, NULL);
+
+  manager = gtk_source_language_manager_get_default ();
+  language_ids = gtk_source_language_manager_get_language_ids (manager);
+  submenus = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref);
+  top_menu = g_menu_new ();
+  top_section = g_menu_new ();
+
+  g_menu_append_section (top_menu, _("Language"), G_MENU_MODEL (top_section));
+
+  for (guint i = 0; language_ids[i]; i++)
+    {
+      const char *language_id = language_ids[i];
+      GtkSourceLanguage *language = gtk_source_language_manager_get_language (manager, language_id);
+      g_autoptr(GMenuItem) item = NULL;
+      const char *name;
+      const char *section;
+      GMenu *submenu;
+
+      if (gtk_source_language_get_hidden (language))
+        continue;
+
+      name = gtk_source_language_get_name (language);
+      section = gtk_source_language_get_section (language);
+
+      if (!(submenu = g_hash_table_lookup (submenus, section)))
+        {
+          submenu = g_menu_new ();
+          g_hash_table_insert (submenus, (char *)section, submenu);
+        }
+
+      item = g_menu_item_new (name, NULL);
+      g_menu_item_set_action_and_target (item, action_name, "s", language_id);
+      g_menu_append_item (submenu, item);
+    }
+
+  sections = (char **)g_hash_table_get_keys_as_array (submenus, &len);
+  ide_strv_sort ((char **)sections, len);
+
+  for (guint i = 0; sections[i]; i++)
+    {
+      GMenu *submenu = g_hash_table_lookup (submenus, sections[i]);
+      g_menu_append_submenu (top_section, sections[i], G_MENU_MODEL (submenu));
+    }
+
+  g_hash_table_unref (submenus);
+
+  return G_MENU_MODEL (top_menu);
+}
diff --git a/src/libide/editor/ide-editor-utils.h b/src/libide/editor/ide-editor-utils.h
new file mode 100644
index 000000000..f776451e6
--- /dev/null
+++ b/src/libide/editor/ide-editor-utils.h
@@ -0,0 +1,41 @@
+/* ide-editor-utils.h
+ *
+ * Copyright 2020 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
+
+#if !defined (IDE_EDITOR_INSIDE) && !defined (IDE_EDITOR_COMPILATION)
+# error "Only <libide-editor.h> can be included directly."
+#endif
+
+#include <gtksourceview/gtksource.h>
+
+#include <libide-core.h>
+
+G_BEGIN_DECLS
+
+void                     ide_editor_file_chooser_add_encodings     (GtkFileChooser       *chooser);
+void                     ide_editor_file_chooser_add_line_endings  (GtkFileChooser       *chooser,
+                                                                    GtkSourceNewlineType  selected);
+const GtkSourceEncoding *ide_editor_file_chooser_get_encoding      (GtkFileChooser       *chooser);
+GtkSourceNewlineType     ide_editor_file_chooser_get_line_ending   (GtkFileChooser       *chooser);
+GMenuModel              *ide_editor_encoding_menu_new              (const char           *action_name);
+GMenuModel              *ide_editor_syntax_menu_new                (const char           *action_name);
+
+G_END_DECLS
diff --git a/src/libide/editor/ide-editor-workspace.c b/src/libide/editor/ide-editor-workspace.c
index 5acb310c8..aaa29c236 100644
--- a/src/libide/editor/ide-editor-workspace.c
+++ b/src/libide/editor/ide-editor-workspace.c
@@ -22,91 +22,253 @@
 
 #include "config.h"
 
-#include "ide-editor-surface.h"
 #include "ide-editor-workspace.h"
+#include "ide-workspace-private.h"
 
 /**
  * SECTION:ide-editor-workspace
  * @title: IdeEditorWorkspace
- * @short_description: A simplified workspace for dedicated editing
+ * @short_description: The editor IDE window
  *
- * The #IdeEditorWorkspace is a secondary workspace that can be used to
- * add additional #IdePage to. It does not contain the full contents of
- * the #IdePrimaryWorkspace. It is suitable for using on an additional
- * monitor as well as a dedicated editor in simplified Builder mode when
- * running directly from the command line.
- *
- * Since: 3.32
+ * The editor workspace is a secondary workspace that may be added to
+ * supplement the IdePrimaryWorkspace for additional editors. It may
+ * also be used in an "editor" mode without a project.
  */
 
 struct _IdeEditorWorkspace
 {
-  IdeWorkspace        parent_instance;
-  DzlMenuButton      *surface_menu_button;
-  DzlShortcutTooltip *search_tooltip;
+  IdeWorkspace       parent_instance;
+
+  /* Template widgets */
+  IdeHeaderBar       *header_bar;
+  AdwWindowTitle     *project_title;
+  GtkMenuButton      *add_button;
+  PanelDock          *dock;
+  PanelPaned         *edge_start;
+  PanelPaned         *edge_end;
+  PanelPaned         *edge_bottom;
+  IdeGrid            *grid;
 };
 
 G_DEFINE_FINAL_TYPE (IdeEditorWorkspace, ide_editor_workspace, IDE_TYPE_WORKSPACE)
 
+static gboolean
+file_to_short_path (GBinding     *binding,
+                    const GValue *from,
+                    GValue       *to,
+                    gpointer      user_data)
+{
+  GFile *file;
+
+  g_assert (G_IS_BINDING (binding));
+  g_assert (G_VALUE_HOLDS (from, G_TYPE_FILE));
+  g_assert (G_VALUE_HOLDS (to, G_TYPE_STRING));
+  g_assert (user_data == NULL);
+
+  if ((file = g_value_get_object (from)))
+    {
+      if (g_file_is_native (file))
+        g_value_take_string (to, ide_path_collapse (g_file_peek_path (file)));
+      else
+        g_value_take_string (to, g_file_get_uri (file));
+    }
+
+  return TRUE;
+}
+
 static void
-ide_editor_workspace_surface_set (IdeWorkspace *workspace,
-                                   IdeSurface   *surface)
+ide_editor_workspace_context_set (IdeWorkspace *workspace,
+                                  IdeContext   *context)
 {
   IdeEditorWorkspace *self = (IdeEditorWorkspace *)workspace;
+  IdeProjectInfo *project_info;
+  IdeWorkbench *workbench;
 
+  g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (IDE_IS_EDITOR_WORKSPACE (self));
-  g_assert (!surface || IDE_IS_SURFACE (surface));
+  g_assert (IDE_IS_CONTEXT (context));
 
-  if (DZL_IS_DOCK_ITEM (surface))
-    {
-      g_autofree gchar *icon_name = NULL;
+  IDE_WORKSPACE_CLASS (ide_editor_workspace_parent_class)->context_set (workspace, context);
 
-      icon_name = dzl_dock_item_get_icon_name (DZL_DOCK_ITEM (surface));
-      g_object_set (self->surface_menu_button,
-                    "icon-name", icon_name,
-                    NULL);
-    }
+  workbench = ide_widget_get_workbench (GTK_WIDGET (self));
+  project_info = ide_workbench_get_project_info (workbench);
 
-  IDE_WORKSPACE_CLASS (ide_editor_workspace_parent_class)->surface_set (workspace, surface);
+  if (project_info)
+    g_object_bind_property (project_info, "name",
+                            self->project_title, "title",
+                            G_BINDING_SYNC_CREATE);
+  g_object_bind_property_full (context, "workdir",
+                               self->project_title, "subtitle",
+                               G_BINDING_SYNC_CREATE,
+                               file_to_short_path, NULL, NULL, NULL);
+}
+
+static void
+ide_editor_workspace_add_page (IdeWorkspace     *workspace,
+                               IdePage          *page,
+                               IdePanelPosition *position)
+{
+  IdeEditorWorkspace *self = (IdeEditorWorkspace *)workspace;
+
+  g_assert (IDE_IS_EDITOR_WORKSPACE (self));
+
+  _ide_workspace_add_widget (workspace,
+                             PANEL_WIDGET (page),
+                             position,
+                             self->edge_start,
+                             self->edge_end,
+                             self->edge_bottom,
+                             self->grid);
+}
+
+static void
+ide_editor_workspace_add_pane (IdeWorkspace     *workspace,
+                               IdePane          *pane,
+                               IdePanelPosition *position)
+{
+  IdeEditorWorkspace *self = (IdeEditorWorkspace *)workspace;
+
+  g_assert (IDE_IS_EDITOR_WORKSPACE (self));
+
+  _ide_workspace_add_widget (workspace,
+                             PANEL_WIDGET (pane),
+                             position,
+                             self->edge_start,
+                             self->edge_end,
+                             self->edge_bottom,
+                             self->grid);
+}
+
+static void
+ide_editor_workspace_add_grid_column (IdeWorkspace *workspace,
+                                      guint         position)
+{
+  panel_grid_insert_column (PANEL_GRID (IDE_EDITOR_WORKSPACE (workspace)->grid), position);
+}
+
+static IdeFrame *
+ide_editor_workspace_get_most_recent_frame (IdeWorkspace *workspace)
+{
+  IdeEditorWorkspace *self = (IdeEditorWorkspace *)workspace;
+
+  g_assert (IDE_IS_EDITOR_WORKSPACE (self));
+
+  return IDE_FRAME (panel_grid_get_most_recent_frame (PANEL_GRID (self->grid)));
+}
+
+static PanelFrame *
+ide_editor_workspace_get_frame_at_position (IdeWorkspace     *workspace,
+                                            IdePanelPosition *position)
+{
+  IdeEditorWorkspace *self = (IdeEditorWorkspace *)workspace;
+
+  g_assert (IDE_IS_EDITOR_WORKSPACE (self));
+  g_assert (position != NULL);
+
+  return _ide_workspace_find_frame (workspace,
+                                    position,
+                                    self->edge_start,
+                                    self->edge_end,
+                                    self->edge_bottom,
+                                    self->grid);
+}
+
+static gboolean
+ide_editor_workspace_can_search (IdeWorkspace *workspace)
+{
+  return TRUE;
+}
+
+static IdeHeaderBar *
+ide_editor_workspace_get_header_bar (IdeWorkspace *workspace)
+{
+  return IDE_EDITOR_WORKSPACE (workspace)->header_bar;
+}
+
+static void
+ide_editor_workspace_foreach_page (IdeWorkspace    *workspace,
+                                   IdePageCallback  callback,
+                                   gpointer         user_data)
+{
+  ide_grid_foreach_page (IDE_EDITOR_WORKSPACE (workspace)->grid, callback, user_data);
+}
+
+static void
+ide_editor_workspace_dispose (GObject *object)
+{
+  IdeEditorWorkspace *self = (IdeEditorWorkspace *)object;
+
+  /* Ensure that the grid is removed first so that it will cleanup
+   * addins/pages/etc before we ever get to removing the workspace
+   * addins as part of the parent class.
+   */
+  panel_dock_remove (self->dock, GTK_WIDGET (self->grid));
+  self->grid = NULL;
+
+  G_OBJECT_CLASS (ide_editor_workspace_parent_class)->dispose (object);
 }
 
 static void
 ide_editor_workspace_class_init (IdeEditorWorkspaceClass *klass)
 {
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
   IdeWorkspaceClass *workspace_class = IDE_WORKSPACE_CLASS (klass);
 
+  object_class->dispose = ide_editor_workspace_dispose;
+
+  workspace_class->add_grid_column = ide_editor_workspace_add_grid_column;
+  workspace_class->add_page = ide_editor_workspace_add_page;
+  workspace_class->add_pane = ide_editor_workspace_add_pane;
+  workspace_class->can_search = ide_editor_workspace_can_search;
+  workspace_class->context_set = ide_editor_workspace_context_set;
+  workspace_class->foreach_page = ide_editor_workspace_foreach_page;
+  workspace_class->get_frame_at_position = ide_editor_workspace_get_frame_at_position;
+  workspace_class->get_header_bar = ide_editor_workspace_get_header_bar;
+  workspace_class->get_most_recent_frame = ide_editor_workspace_get_most_recent_frame;
+
   ide_workspace_class_set_kind (workspace_class, "editor");
 
-  workspace_class->surface_set = ide_editor_workspace_surface_set;
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/libide-editor/ide-editor-workspace.ui");
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorWorkspace, add_button);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorWorkspace, dock);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorWorkspace, edge_bottom);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorWorkspace, edge_end);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorWorkspace, edge_start);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorWorkspace, grid);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorWorkspace, header_bar);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorWorkspace, project_title);
 
-  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/libide-editor/ui/ide-editor-workspace.ui");
-  gtk_widget_class_bind_template_child (widget_class, IdeEditorWorkspace, surface_menu_button);
-  gtk_widget_class_bind_template_child (widget_class, IdeEditorWorkspace, search_tooltip);
+  g_type_ensure (IDE_TYPE_GRID);
+  g_type_ensure (IDE_TYPE_NOTIFICATIONS_BUTTON);
+  g_type_ensure (IDE_TYPE_OMNI_BAR);
 }
 
 static void
 ide_editor_workspace_init (IdeEditorWorkspace *self)
 {
+  GMenu *menu;
+
   gtk_widget_init_template (GTK_WIDGET (self));
+
+  menu = ide_application_get_menu_by_id (IDE_APPLICATION_DEFAULT, "new-document-menu");
+  gtk_menu_button_set_menu_model (self->add_button, G_MENU_MODEL (menu));
 }
 
 /**
  * ide_editor_workspace_new:
- * @app: an #IdeApplication
- *
- * Creates a new #IdeEditorWorkspace.
+ * @application: an #IdeApplication such as %IDE_APPLICATION_DEFAULT
  *
- * You'll need to add this to a workbench to be functional.
+ * Creates a new #IdeEditorWorkspace
  *
  * Returns: (transfer full): an #IdeEditorWorkspace
- *
- * Since: 3.32
  */
 IdeEditorWorkspace *
-ide_editor_workspace_new (IdeApplication *app)
+ide_editor_workspace_new (IdeApplication *application)
 {
+  g_return_val_if_fail (IDE_IS_APPLICATION (application), NULL);
+
   return g_object_new (IDE_TYPE_EDITOR_WORKSPACE,
-                       "application", app,
+                       "application", application,
                        NULL);
 }
diff --git a/src/libide/editor/ide-editor-workspace.h b/src/libide/editor/ide-editor-workspace.h
index 48a4472c1..6c5e4c0df 100644
--- a/src/libide/editor/ide-editor-workspace.h
+++ b/src/libide/editor/ide-editor-workspace.h
@@ -24,16 +24,16 @@
 # error "Only <libide-editor.h> can be included directly."
 #endif
 
-#include <libide-core.h>
+#include <libide-gui.h>
 
 G_BEGIN_DECLS
 
 #define IDE_TYPE_EDITOR_WORKSPACE (ide_editor_workspace_get_type())
 
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 G_DECLARE_FINAL_TYPE (IdeEditorWorkspace, ide_editor_workspace, IDE, EDITOR_WORKSPACE, IdeWorkspace)
 
-IDE_AVAILABLE_IN_3_32
-IdeEditorWorkspace *ide_editor_workspace_new (IdeApplication *app);
+IDE_AVAILABLE_IN_ALL
+IdeEditorWorkspace *ide_editor_workspace_new (IdeApplication *application);
 
 G_END_DECLS
diff --git a/src/libide/editor/ide-editor-workspace.ui b/src/libide/editor/ide-editor-workspace.ui
index baa2436df..4c37571f2 100644
--- a/src/libide/editor/ide-editor-workspace.ui
+++ b/src/libide/editor/ide-editor-workspace.ui
@@ -2,67 +2,80 @@
 <interface>
   <template class="IdeEditorWorkspace" parent="IdeWorkspace">
     <child type="titlebar">
-      <object class="IdeHeaderBar">
-        <property name="show-close-button">true</property>
-        <property name="show-fullscreen-button">false</property>
+      <object class="IdeHeaderBar" id="header_bar">
         <property name="menu-id">ide-editor-workspace-menu</property>
-        <property name="visible">true</property>
-        <child type="primary">
-          <object class="IdeSurfacesButton" id="surface_menu_button">
-            <property name="focus-on-click">false</property>
-            <property name="menu-id">ide-editor-workspace-surfaces-menu</property>
-            <property name="show-accels">true</property>
-            <property name="show-arrow">true</property>
-            <property name="show-icons">true</property>
-            <!-- disable transitions since they'll cause jitter with the
-                 whole surface changing below it. -->
-            <property name="transitions-enabled">false</property>
-            <property name="has-tooltip">true</property>
-            <property name="tooltip-text" translatable="yes">Switch surface</property>
+        <child type="left">
+          <object class="GtkMenuButton" id="add_button">
+            <property name="icon-name">list-add-symbolic</property>
+            <property name="always-show-arrow">true</property>
           </object>
         </child>
-        <child type="secondary">
-          <object class="DzlPriorityBox">
-            <property name="visible">true</property>
-            <child>
-              <object class="IdeSearchButton" id="search_button">
-                <property name="visible">true</property>
-                <child internal-child="entry">
-                  <object class="DzlSuggestionEntry">
-                    <property name="max-width-chars">20</property>
-                  </object>
-                </child>
-              </object>
-              <packing>
-                <property name="pack-type">end</property>
-                <property name="priority">-200</property>
-              </packing>
-            </child>
+        <child type="left">
+          <object class="PanelDockSwitcher">
+            <property name="dock">dock</property>
+            <property name="position">start</property>
+          </object>
+        </child>
+        <child type="title">
+          <object class="AdwWindowTitle" id="project_title">
+            <property name="title" translatable="yes">Builder</property>
+          </object>
+        </child>
+        <child type="right">
+          <object class="IdeNotificationsButton" id="notifications_button"/>
+        </child>
+        <child type="right">
+          <object class="GtkButton" id="search_button">
+            <property name="icon-name">edit-find-symbolic</property>
+          </object>
+        </child>
+        <child type="right">
+          <object class="PanelDockSwitcher">
+            <property name="dock">dock</property>
+            <property name="position">end</property>
+          </object>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="PanelDock" id="dock">
+        <property name="start-width">300</property>
+        <property name="reveal-start">true</property>
+        <property name="vexpand">true</property>
+        <child type="center">
+          <object class="IdeGrid" id="grid">
+          </object>
+        </child>
+        <child type="start">
+          <object class="PanelPaned" id="edge_start">
+            <property name="orientation">vertical</property>
+          </object>
+        </child>
+        <child type="end">
+          <object class="PanelPaned" id="edge_end">
+            <property name="orientation">vertical</property>
             <child>
-              <object class="GtkRevealer">
-                <property name="reveal-child">false</property>
-                <property name="transition-type">slide-left</property>
-                <property name="visible">true</property>
-                <child>
-                  <object class="IdeNotificationsButton" id="notifications_button">
-                    <property name="show-theatric">false</property>
-                    <property name="visible">true</property>
-                  </object>
-                </child>
+              <object class="PanelFrame">
               </object>
-              <packing>
-                <property name="pack-type">end</property>
-                <property name="priority">-300</property>
-              </packing>
             </child>
           </object>
         </child>
+        <child type="bottom">
+          <object class="PanelPaned" id="edge_bottom">
+            <property name="orientation">horizontal</property>
+          </object>
+        </child>
+      </object>
+    </child>
+    <child internal-child="statusbar">
+      <object class="PanelStatusbar">
+        <child type="suffix">
+          <object class="PanelDockSwitcher">
+            <property name="dock">dock</property>
+            <property name="position">bottom</property>
+          </object>
+        </child>
       </object>
     </child>
   </template>
-  <object class="DzlShortcutTooltip" id="search_tooltip">
-    <property name="title" translatable="yes">Search your project</property>
-    <property name="command-id">org.gnome.builder.workspace.global-search</property>
-    <property name="widget">search_button</property>
-  </object>
 </interface>
diff --git a/src/libide/editor/ide-editor.c b/src/libide/editor/ide-editor.c
new file mode 100644
index 000000000..64cbf886d
--- /dev/null
+++ b/src/libide/editor/ide-editor.c
@@ -0,0 +1,260 @@
+/* ide-editor.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 "ide-editor"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "ide-editor.h"
+#include "ide-editor-page.h"
+
+typedef struct _Focus
+{
+  IdeWorkspace     *workspace;
+  IdePanelPosition *position;
+  IdeLocation      *location;
+  IdeBuffer        *buffer;
+  GFile            *file;
+} Focus;
+
+static Focus *
+focus_new (IdeWorkspace     *workspace,
+           IdePanelPosition *position,
+           IdeBuffer        *buffer,
+           IdeLocation      *location)
+{
+  IdeBufferManager *bufmgr;
+  IdeContext *context;
+  Focus *focus;
+  GFile *file = NULL;
+
+  g_assert (IDE_IS_WORKSPACE (workspace));
+  g_assert (position != NULL);
+  g_assert (!buffer || IDE_IS_BUFFER (buffer));
+  g_assert (!location || IDE_IS_LOCATION (location));
+  g_assert (buffer != NULL || location != NULL);
+
+  context = ide_workspace_get_context (workspace);
+  bufmgr = ide_buffer_manager_from_context (context);
+
+  g_assert (IDE_IS_CONTEXT (context));
+  g_assert (IDE_IS_BUFFER_MANAGER (bufmgr));
+
+  if (location != NULL)
+    file = ide_location_get_file (location);
+  else
+    file = ide_buffer_get_file (buffer);
+
+  g_assert (buffer != NULL || file != NULL);
+
+  if (buffer == NULL)
+    buffer = ide_buffer_manager_find_buffer (bufmgr, file);
+
+  focus = g_atomic_rc_box_alloc0 (sizeof *focus);
+  focus->position = ide_panel_position_ref (position);
+  g_set_object (&focus->workspace, workspace);
+  g_set_object (&focus->buffer, buffer);
+  g_set_object (&focus->location, location);
+  g_set_object (&focus->file, file);
+
+  return focus;
+}
+
+static void
+focus_finalize (gpointer data)
+{
+  Focus *focus = data;
+
+  g_clear_object (&focus->workspace);
+  g_clear_object (&focus->location);
+  g_clear_object (&focus->buffer);
+  g_clear_object (&focus->file);
+}
+
+static void
+focus_free (Focus *focus)
+{
+  g_atomic_rc_box_release_full (focus, focus_finalize);
+}
+
+static void
+focus_complete (Focus        *focus,
+                const GError *error)
+{
+  IdeEditorPage *page = NULL;
+  PanelFrame *frame;
+
+  IDE_ENTRY;
+
+  g_assert (focus != NULL);
+  g_assert (G_IS_FILE (focus->file));
+  g_assert (!focus->location || IDE_IS_LOCATION (focus->location));
+  g_assert (!focus->buffer || IDE_IS_BUFFER (focus->buffer));
+  g_assert (focus->buffer || error != NULL);
+  g_assert (IDE_IS_WORKSPACE (focus->workspace));
+  g_assert (focus->position != NULL);
+
+  if (error != NULL)
+    {
+      IdeContext *context = ide_workspace_get_context (focus->workspace);
+      ide_context_warning (context,
+                           /* translators: %s is replaced with the error message */
+                           _("Failed to open file: %s"),
+                           error->message);
+      focus_free (focus);
+      IDE_EXIT;
+    }
+
+  frame = ide_workspace_get_frame_at_position (focus->workspace, focus->position);
+
+  if (frame != NULL)
+    {
+      guint n_pages = panel_frame_get_n_pages (PANEL_FRAME (frame));
+
+      for (guint i = 0; i < n_pages; i++)
+        {
+          PanelWidget *child = panel_frame_get_page (PANEL_FRAME (frame), i);
+
+          if (IDE_IS_EDITOR_PAGE (child))
+            {
+              IdeBuffer *buffer = ide_editor_page_get_buffer (IDE_EDITOR_PAGE (child));
+
+              if (buffer == focus->buffer)
+                {
+                  page = IDE_EDITOR_PAGE (child);
+                  break;
+                }
+            }
+        }
+    }
+
+  g_assert (!page || IDE_IS_EDITOR_PAGE (page));
+
+  if (page == NULL)
+    {
+      page = IDE_EDITOR_PAGE (ide_editor_page_new (focus->buffer));
+      ide_workspace_add_page (focus->workspace, IDE_PAGE (page), focus->position);
+    }
+
+  if (focus->location != NULL)
+    {
+      IdeSourceView *view = ide_editor_page_get_view (page);
+      GtkTextIter iter;
+
+      ide_buffer_get_iter_at_location (focus->buffer, &iter, focus->location);
+      gtk_text_buffer_select_range (GTK_TEXT_BUFFER (focus->buffer), &iter, &iter);
+      ide_source_view_scroll_to_insert (view);
+    }
+
+  if (frame != NULL)
+    panel_frame_set_visible_child (frame, PANEL_WIDGET (page));
+
+  gtk_widget_grab_focus (GTK_WIDGET (page));
+
+  focus_free (focus);
+
+  IDE_EXIT;
+}
+
+static void
+ide_editor_load_file_cb (GObject      *object,
+                         GAsyncResult *result,
+                         gpointer      user_data)
+{
+  IdeBufferManager *bufmgr = (IdeBufferManager *)object;
+  g_autoptr(IdeBuffer) buffer = NULL;
+  g_autoptr(GError) error = NULL;
+  Focus *focus = user_data;
+
+  g_assert (IDE_IS_BUFFER_MANAGER (bufmgr));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (focus != NULL);
+  g_assert (IDE_IS_WORKSPACE (focus->workspace));
+  g_assert (focus->position != NULL);
+  g_assert (G_IS_FILE (focus->file));
+
+  if ((buffer = ide_buffer_manager_load_file_finish (bufmgr, result, &error)))
+    g_set_object (&focus->buffer, buffer);
+
+  focus_complete (focus, error);
+}
+
+static void
+do_focus (IdeWorkspace     *workspace,
+          IdePanelPosition *position,
+          IdeBuffer        *buffer,
+          IdeLocation      *location)
+{
+  g_autoptr(IdePanelPosition) local_position = NULL;
+  IdeBufferManager *bufmgr;
+  IdeContext *context;
+  Focus *focus;
+
+  g_assert (IDE_IS_WORKSPACE (workspace));
+  g_assert (!buffer || IDE_IS_BUFFER (buffer));
+  g_assert (!location || IDE_IS_LOCATION (location));
+  g_assert (buffer != NULL || location != NULL);
+
+  context = ide_workspace_get_context (workspace);
+  bufmgr = ide_buffer_manager_from_context (context);
+
+  g_assert (IDE_IS_CONTEXT (context));
+  g_assert (IDE_IS_BUFFER_MANAGER (bufmgr));
+
+  if (position == NULL)
+    position = local_position = ide_panel_position_new ();
+
+  focus = focus_new (workspace, position, buffer, location);
+
+  if (focus->buffer == NULL)
+    ide_buffer_manager_load_file_async (bufmgr,
+                                        focus->file,
+                                        IDE_BUFFER_OPEN_FLAGS_NONE,
+                                        NULL,
+                                        ide_workspace_get_cancellable (workspace),
+                                        ide_editor_load_file_cb,
+                                        focus);
+  else
+    focus_complete (focus, NULL);
+}
+
+void
+ide_editor_focus_location (IdeWorkspace     *workspace,
+                           IdePanelPosition *position,
+                           IdeLocation      *location)
+{
+  g_return_if_fail (IDE_IS_WORKSPACE (workspace));
+  g_return_if_fail (IDE_IS_LOCATION (location));
+
+  do_focus (workspace, position, NULL, location);
+}
+
+void
+ide_editor_focus_buffer (IdeWorkspace     *workspace,
+                         IdePanelPosition *position,
+                         IdeBuffer        *buffer)
+{
+  g_return_if_fail (IDE_IS_WORKSPACE (workspace));
+  g_return_if_fail (IDE_IS_BUFFER (buffer));
+
+  do_focus (workspace, position, buffer, NULL);
+}
diff --git a/src/libide/editor/ide-editor-utilities.h b/src/libide/editor/ide-editor.h
similarity index 61%
rename from src/libide/editor/ide-editor-utilities.h
rename to src/libide/editor/ide-editor.h
index 8893f35ec..a908116de 100644
--- a/src/libide/editor/ide-editor-utilities.h
+++ b/src/libide/editor/ide-editor.h
@@ -1,6 +1,6 @@
-/* ide-editor-utilities.h
+/* ide-editor.h
  *
- * Copyright 2017-2019 Christian Hergert <chergert redhat com>
+ * 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
@@ -20,20 +20,19 @@
 
 #pragma once
 
-#if !defined (IDE_EDITOR_INSIDE) && !defined (IDE_EDITOR_COMPILATION)
-# error "Only <libide-editor.h> can be included directly."
-#endif
-
 #include <libide-core.h>
+#include <libide-code.h>
 #include <libide-gui.h>
 
 G_BEGIN_DECLS
 
-#define IDE_TYPE_EDITOR_UTILITIES (ide_editor_utilities_get_type())
-
-IDE_AVAILABLE_IN_3_32
-G_DECLARE_FINAL_TYPE (IdeEditorUtilities, ide_editor_utilities, IDE, EDITOR_UTILITIES, IdePanel)
-
-/* Use GtkContainer api to add your DzlDockWidget */
+IDE_AVAILABLE_IN_ALL
+void ide_editor_focus_location (IdeWorkspace     *workspace,
+                                IdePanelPosition *position,
+                                IdeLocation      *location);
+IDE_AVAILABLE_IN_ALL
+void ide_editor_focus_buffer   (IdeWorkspace     *workspace,
+                                IdePanelPosition *position,
+                                IdeBuffer        *buffer);
 
 G_END_DECLS
diff --git a/src/libide/editor/libide-editor.gresource.xml b/src/libide/editor/libide-editor.gresource.xml
index 2c390f7e5..8ceb752e9 100644
--- a/src/libide/editor/libide-editor.gresource.xml
+++ b/src/libide/editor/libide-editor.gresource.xml
@@ -1,11 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <gresources>
-  <gresource prefix="/org/gnome/libide-editor/ui/">
+  <gresource prefix="/org/gnome/libide-editor/">
     <file preprocess="xml-stripblanks">ide-editor-page.ui</file>
-    <file preprocess="xml-stripblanks">ide-editor-search-bar.ui</file>
-    <file preprocess="xml-stripblanks">ide-editor-settings-dialog.ui</file>
-    <file preprocess="xml-stripblanks">ide-editor-sidebar.ui</file>
-    <file preprocess="xml-stripblanks">ide-editor-surface.ui</file>
     <file preprocess="xml-stripblanks">ide-editor-workspace.ui</file>
+    <file preprocess="xml-stripblanks">gtk/menus.ui</file>
+    <file>style.css</file>
   </gresource>
 </gresources>
diff --git a/src/libide/editor/libide-editor.h b/src/libide/editor/libide-editor.h
index b541f5833..c1d6fe856 100644
--- a/src/libide/editor/libide-editor.h
+++ b/src/libide/editor/libide-editor.h
@@ -1,6 +1,6 @@
 /* libide-editor.h
  *
- * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ * Copyright 2018-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
@@ -20,22 +20,10 @@
 
 #pragma once
 
-#include <libide-gui.h>
-#include <libide-sourceview.h>
-
-G_BEGIN_DECLS
-
 #define IDE_EDITOR_INSIDE
-
-#include "ide-editor-addin.h"
-#include "ide-editor-page.h"
-#include "ide-editor-page-addin.h"
-#include "ide-editor-search.h"
-#include "ide-editor-sidebar.h"
-#include "ide-editor-surface.h"
-#include "ide-editor-utilities.h"
-#include "ide-editor-workspace.h"
-
+# include "ide-editor.h"
+# include "ide-editor-page.h"
+# include "ide-editor-page-addin.h"
+# include "ide-editor-utils.h"
+# include "ide-editor-workspace.h"
 #undef IDE_EDITOR_INSIDE
-
-G_END_DECLS
diff --git a/src/libide/editor/meson.build b/src/libide/editor/meson.build
index 808ed784f..c6eb702b8 100644
--- a/src/libide/editor/meson.build
+++ b/src/libide/editor/meson.build
@@ -11,21 +11,16 @@ libide_editor_generated_headers = []
 #
 
 libide_editor_public_headers = [
-  'ide-editor-addin.h',
+  'ide-editor.h',
   'ide-editor-page.h',
   'ide-editor-page-addin.h',
-  'ide-editor-search.h',
-  'ide-editor-sidebar.h',
-  'ide-editor-surface.h',
-  'ide-editor-utilities.h',
+  'ide-editor-utils.h',
   'ide-editor-workspace.h',
   'libide-editor.h',
 ]
 
 libide_editor_private_headers = [
-  'ide-editor-print-operation.h',
-  'ide-editor-search-bar.h',
-  'ide-editor-settings-dialog.h',
+  'ide-editor-page-private.h',
 ]
 
 install_headers(libide_editor_public_headers, subdir: libide_editor_header_subdir)
@@ -35,27 +30,18 @@ install_headers(libide_editor_public_headers, subdir: libide_editor_header_subdi
 #
 
 libide_editor_public_sources = [
-  'ide-editor-addin.c',
+  'ide-editor.c',
   'ide-editor-page.c',
   'ide-editor-page-addin.c',
-  'ide-editor-search.c',
-  'ide-editor-sidebar.c',
-  'ide-editor-surface.c',
-  'ide-editor-utilities.c',
+  'ide-editor-utils.c',
   'ide-editor-workspace.c',
 ]
 
 
 libide_editor_private_sources = [
+  'ide-editor-init.c',
   'ide-editor-page-actions.c',
   'ide-editor-page-settings.c',
-  'ide-editor-page-shortcuts.c',
-  'ide-editor-print-operation.c',
-  'ide-editor-search-bar.c',
-  'ide-editor-search-bar-shortcuts.c',
-  'ide-editor-settings-dialog.c',
-  'ide-editor-surface-actions.c',
-  'ide-editor-surface-shortcuts.c',
 ]
 
 libide_editor_sources += libide_editor_public_sources
@@ -80,11 +66,11 @@ libide_editor_sources += libide_editor_resources
 libide_editor_deps = [
   libgio_dep,
   libgtk_dep,
-  libdazzle_dep,
   libpeas_dep,
 
   libide_core_dep,
   libide_io_dep,
+  libide_plugins_dep,
   libide_projects_dep,
   libide_search_dep,
   libide_sourceview_dep,
@@ -93,7 +79,6 @@ libide_editor_deps = [
 ]
 
 libide_editor_internal_deps = [
-  libpangoft2_dep,
 ]
 
 #
diff --git a/src/libide/editor/style.css b/src/libide/editor/style.css
new file mode 100644
index 000000000..d958de97d
--- /dev/null
+++ b/src/libide/editor/style.css
@@ -0,0 +1,12 @@
+textview.GtkSourceMap {
+  font-family: BuilderBlocks;
+  font-size: 1.75pt;
+  line-height: 5px;
+  color: alpha(currentColor, 0.75);
+}
+textview.GtkSourceMap:dir(ltr) {
+  border-left: 1px solid @borders;
+}
+textview.GtkSourceMap:dir(rtl) {
+  border-right: 1px solid @borders;
+}


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