[gnome-builder/wip/chergert/perspective] libide: start porting the editor to libide



commit fee07257d61cafa0abad0216474745762c975924
Author: Christian Hergert <chergert redhat com>
Date:   Mon Nov 16 02:32:13 2015 -0800

    libide: start porting the editor to libide
    
    Ideally, this will become an internal plugin so we don't need to
    
     1) move it to plugins/
     2) create interfaces for everything
    
    However, maybe we want to do #2 anyway, but that could turn out to be
    a lot of work and reduce flexability of plugins.

 contrib/egg/Makefile.am                            |    2 +
 contrib/egg/egg-simple-popover.c                   |  415 +++++++++
 contrib/egg/egg-simple-popover.h                   |   90 ++
 .../egg/egg-simple-popover.ui                      |    2 +-
 contrib/egg/egg.gresource.xml                      |    1 +
 data/ui/gb-editor-settings-widget.ui               |  292 ------
 data/ui/gb-greeter-project-row.ui                  |   96 --
 data/ui/gb-greeter-window.ui                       |  302 -------
 data/ui/gb-preferences-page-editor.ui              |  295 -------
 data/ui/gb-preferences-page-git.ui                 |   88 --
 data/ui/gb-preferences-page-insight.ui             |   99 ---
 data/ui/gb-preferences-page-keybindings.ui         |  101 ---
 data/ui/gb-preferences-page-language.ui            |   84 --
 data/ui/gb-preferences-page-plugins.ui             |   36 -
 data/ui/gb-preferences-page-theme.ui               |   61 --
 data/ui/gb-preferences-switch.ui                   |   71 --
 data/ui/gb-preferences-window.ui                   |  198 -----
 data/ui/gb-recent-project-row.ui                   |   81 --
 data/ui/gb-view-stack.ui                           |  262 ------
 data/ui/gb-view.ui                                 |   71 --
 data/ui/gb-workbench.ui                            |  165 ----
 data/ui/gb-workspace-pane.ui                       |   42 -
 data/ui/gb-workspace.ui                            |   30 -
 .../ui/{gb-editor-frame.ui => ide-editor-frame.ui} |    4 +-
 ...-tweak-widget.ui => ide-editor-tweak-widget.ui} |    2 +-
 data/ui/{gb-editor-view.ui => ide-editor-view.ui}  |    8 +-
 libide/Makefile.am                                 |   25 +
 libide/editor/ide-editor-frame-actions.c           |  117 +++
 libide/editor/ide-editor-frame-actions.h           |   30 +
 libide/editor/ide-editor-frame-private.h           |   58 ++
 libide/editor/ide-editor-frame.c                   |  926 +++++++++++++++++++
 libide/editor/ide-editor-frame.h                   |   38 +
 libide/editor/ide-editor-map-bin.c                 |  245 +++++
 libide/editor/ide-editor-map-bin.h                 |   32 +
 libide/editor/ide-editor-print-operation.c         |  207 +++++
 libide/editor/ide-editor-print-operation.h         |   34 +
 libide/editor/ide-editor-tweak-widget.c            |  184 ++++
 libide/editor/ide-editor-tweak-widget.h            |   33 +
 libide/editor/ide-editor-view-actions.c            |  824 +++++++++++++++++
 libide/editor/ide-editor-view-actions.h            |   31 +
 libide/editor/ide-editor-view-addin-private.h      |   34 +
 libide/editor/ide-editor-view-addin.c              |   64 ++
 libide/editor/ide-editor-view-addin.h              |   44 +
 libide/editor/ide-editor-view-private.h            |   60 ++
 libide/editor/ide-editor-view.c                    |  932 ++++++++++++++++++++
 libide/editor/ide-editor-view.h                    |   32 +
 libide/editor/run.sh                               |   26 +
 libide/resources/libide.gresource.xml              |    3 +
 libide/util/ide-dnd.c                              |   42 +
 libide/util/ide-dnd.h                              |   30 +
 libide/util/ide-gtk.c                              |   96 ++
 libide/util/ide-gtk.h                              |   16 +-
 plugins/command-bar/gb-command-provider.c          |   10 +-
 plugins/command-bar/gb-command-vim-provider.c      |    2 +-
 54 files changed, 4678 insertions(+), 2395 deletions(-)
---
diff --git a/contrib/egg/Makefile.am b/contrib/egg/Makefile.am
index 19fad1a..63761d4 100644
--- a/contrib/egg/Makefile.am
+++ b/contrib/egg/Makefile.am
@@ -34,6 +34,8 @@ libegg_private_la_SOURCES = \
        egg-settings-sandwich.h \
        egg-signal-group.c \
        egg-signal-group.h \
+       egg-simple-popover.c \
+       egg-simple-popover.h \
        egg-slider.c \
        egg-slider.h \
        egg-state-machine.c \
diff --git a/contrib/egg/egg-simple-popover.c b/contrib/egg/egg-simple-popover.c
new file mode 100644
index 0000000..d473a7b
--- /dev/null
+++ b/contrib/egg/egg-simple-popover.c
@@ -0,0 +1,415 @@
+/* egg-simple-popover.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "egg-simple-popover.h"
+
+typedef struct
+{
+  GtkPopover parent_instance;
+
+  GtkLabel  *title;
+  GtkLabel  *message;
+  GtkEntry  *entry;
+  GtkButton *button;
+} EggSimplePopoverPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (EggSimplePopover, egg_simple_popover, GTK_TYPE_POPOVER)
+
+enum {
+  PROP_0,
+  PROP_BUTTON_TEXT,
+  PROP_MESSAGE,
+  PROP_READY,
+  PROP_TEXT,
+  PROP_TITLE,
+  LAST_PROP
+};
+
+enum {
+  ACTIVATE,
+  CHANGED,
+  INSERT_TEXT,
+  LAST_SIGNAL
+};
+
+static GParamSpec *properties [LAST_PROP];
+static guint signals [LAST_SIGNAL];
+
+const gchar *
+egg_simple_popover_get_button_text (EggSimplePopover *self)
+{
+  EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+
+  g_return_val_if_fail (EGG_IS_SIMPLE_POPOVER (self), NULL);
+
+  return gtk_button_get_label (priv->button);
+}
+
+void
+egg_simple_popover_set_button_text (EggSimplePopover *self,
+                                   const gchar     *button_text)
+{
+  EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+
+  g_return_if_fail (EGG_IS_SIMPLE_POPOVER (self));
+
+  gtk_button_set_label (priv->button, button_text);
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUTTON_TEXT]);
+}
+
+const gchar *
+egg_simple_popover_get_message (EggSimplePopover *self)
+{
+  EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+
+  g_return_val_if_fail (EGG_IS_SIMPLE_POPOVER (self), NULL);
+
+  return gtk_label_get_text (priv->message);
+}
+
+void
+egg_simple_popover_set_message (EggSimplePopover *self,
+                               const gchar     *message)
+{
+  EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+
+  g_return_if_fail (EGG_IS_SIMPLE_POPOVER (self));
+
+  gtk_label_set_label (priv->message, message);
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MESSAGE]);
+}
+
+gboolean
+egg_simple_popover_get_ready (EggSimplePopover *self)
+{
+  EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+
+  g_return_val_if_fail (EGG_IS_SIMPLE_POPOVER (self), FALSE);
+
+  return gtk_widget_get_sensitive (GTK_WIDGET (priv->button));
+}
+
+void
+egg_simple_popover_set_ready (EggSimplePopover *self,
+                             gboolean         ready)
+{
+  EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+
+  g_return_if_fail (EGG_IS_SIMPLE_POPOVER (self));
+
+  gtk_widget_set_sensitive (GTK_WIDGET (priv->button), ready);
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_READY]);
+}
+
+const gchar *
+egg_simple_popover_get_text (EggSimplePopover *self)
+{
+  EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+
+  g_return_val_if_fail (EGG_IS_SIMPLE_POPOVER (self), NULL);
+
+  return gtk_entry_get_text (priv->entry);
+}
+
+void
+egg_simple_popover_set_text (EggSimplePopover *self,
+                            const gchar     *text)
+{
+  EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+
+  g_return_if_fail (EGG_IS_SIMPLE_POPOVER (self));
+
+  gtk_entry_set_text (priv->entry, text);
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TEXT]);
+}
+
+const gchar *
+egg_simple_popover_get_title (EggSimplePopover *self)
+{
+  EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+
+  g_return_val_if_fail (EGG_IS_SIMPLE_POPOVER (self), NULL);
+
+  return gtk_label_get_label (priv->title);
+}
+
+void
+egg_simple_popover_set_title (EggSimplePopover *self,
+                             const gchar     *title)
+{
+  EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+
+  g_return_if_fail (EGG_IS_SIMPLE_POPOVER (self));
+
+  gtk_label_set_label (priv->title, title);
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TITLE]);
+}
+
+static void
+egg_simple_popover_button_clicked (EggSimplePopover *self,
+                                  GtkButton       *button)
+{
+  EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+  const gchar *text;
+
+  g_assert (EGG_IS_SIMPLE_POPOVER (self));
+  g_assert (GTK_IS_BUTTON (button));
+
+  text = gtk_entry_get_text (GTK_ENTRY (priv->entry));
+  g_signal_emit (self, signals [ACTIVATE], 0, text);
+  gtk_widget_hide (GTK_WIDGET (self));
+}
+
+static void
+egg_simple_popover_entry_activate (EggSimplePopover *self,
+                                  GtkEntry        *entry)
+{
+  EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+
+  g_assert (EGG_IS_SIMPLE_POPOVER (self));
+  g_assert (GTK_IS_ENTRY (entry));
+
+  if (egg_simple_popover_get_ready (self))
+    gtk_widget_activate (GTK_WIDGET (priv->button));
+}
+
+static void
+egg_simple_popover_entry_changed (EggSimplePopover *self,
+                                 GtkEntry        *entry)
+{
+  g_assert (EGG_IS_SIMPLE_POPOVER (self));
+  g_assert (GTK_IS_ENTRY (entry));
+
+  g_signal_emit (self, signals [CHANGED], 0);
+}
+
+static void
+egg_simple_popover_entry_insert_text (EggSimplePopover *self,
+                                     gchar           *new_text,
+                                     gint             new_text_length,
+                                     gint            *position,
+                                     GtkEntry        *entry)
+{
+  gboolean ret = GDK_EVENT_PROPAGATE;
+  guint pos;
+  guint n_chars;
+
+  g_assert (EGG_IS_SIMPLE_POPOVER (self));
+  g_assert (new_text != NULL);
+  g_assert (position != NULL);
+
+  pos = *position;
+  n_chars = (new_text_length >= 0) ? new_text_length : g_utf8_strlen (new_text, -1);
+
+  g_signal_emit (self, signals [INSERT_TEXT], 0, pos, new_text, n_chars, &ret);
+
+  if (ret == GDK_EVENT_STOP)
+    g_signal_stop_emission_by_name (entry, "insert-text");
+}
+
+static void
+egg_simple_popover_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  EggSimplePopover *self = EGG_SIMPLE_POPOVER (object);
+
+  switch (prop_id)
+    {
+    case PROP_BUTTON_TEXT:
+      g_value_set_string (value, egg_simple_popover_get_button_text (self));
+      break;
+
+    case PROP_MESSAGE:
+      g_value_set_string (value, egg_simple_popover_get_message (self));
+      break;
+
+    case PROP_READY:
+      g_value_set_boolean (value, egg_simple_popover_get_ready (self));
+      break;
+
+    case PROP_TEXT:
+      g_value_set_string (value, egg_simple_popover_get_text (self));
+      break;
+
+    case PROP_TITLE:
+      g_value_set_string (value, egg_simple_popover_get_title (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+egg_simple_popover_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  EggSimplePopover *self = EGG_SIMPLE_POPOVER (object);
+
+  switch (prop_id)
+    {
+    case PROP_BUTTON_TEXT:
+      egg_simple_popover_set_button_text (self, g_value_get_string (value));
+      break;
+
+    case PROP_MESSAGE:
+      egg_simple_popover_set_message (self, g_value_get_string (value));
+      break;
+
+    case PROP_READY:
+      egg_simple_popover_set_ready (self, g_value_get_boolean (value));
+      break;
+
+    case PROP_TEXT:
+      egg_simple_popover_set_text (self, g_value_get_string (value));
+      break;
+
+    case PROP_TITLE:
+      egg_simple_popover_set_title (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+egg_simple_popover_class_init (EggSimplePopoverClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->get_property = egg_simple_popover_get_property;
+  object_class->set_property = egg_simple_popover_set_property;
+
+  properties [PROP_BUTTON_TEXT] =
+    g_param_spec_string ("button-text",
+                         "Button Text",
+                         "Button Text",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_MESSAGE] =
+    g_param_spec_string ("message",
+                         "Message",
+                         "Message",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_READY] =
+    g_param_spec_boolean ("ready",
+                          "Ready",
+                          "Ready",
+                          FALSE,
+                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_TEXT] =
+    g_param_spec_string ("text",
+                         "Text",
+                         "Text",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_TITLE] =
+    g_param_spec_string ("title",
+                         "Title",
+                         "Title",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+  signals [ACTIVATE] =
+    g_signal_new ("activate",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (EggSimplePopoverClass, activate),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE,
+                  1,
+                  G_TYPE_STRING);
+
+  signals [CHANGED] =
+    g_signal_new ("changed",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (EggSimplePopoverClass, insert_text),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE,
+                  0);
+
+  signals [INSERT_TEXT] =
+    g_signal_new ("insert-text",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (EggSimplePopoverClass, insert_text),
+                  NULL, NULL, NULL,
+                  G_TYPE_BOOLEAN,
+                  3,
+                  G_TYPE_UINT,
+                  G_TYPE_STRING,
+                  G_TYPE_UINT);
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/builder/ui/egg-simple-popover.ui");
+  gtk_widget_class_bind_template_child_private (widget_class, EggSimplePopover, title);
+  gtk_widget_class_bind_template_child_private (widget_class, EggSimplePopover, message);
+  gtk_widget_class_bind_template_child_private (widget_class, EggSimplePopover, entry);
+  gtk_widget_class_bind_template_child_private (widget_class, EggSimplePopover, button);
+}
+
+static void
+egg_simple_popover_init (EggSimplePopover *self)
+{
+  EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  g_signal_connect_object (priv->button,
+                           "clicked",
+                           G_CALLBACK (egg_simple_popover_button_clicked),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (priv->entry,
+                           "changed",
+                           G_CALLBACK (egg_simple_popover_entry_changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (priv->entry,
+                           "activate",
+                           G_CALLBACK (egg_simple_popover_entry_activate),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (priv->entry,
+                           "insert-text",
+                           G_CALLBACK (egg_simple_popover_entry_insert_text),
+                           self,
+                           G_CONNECT_SWAPPED);
+}
+
+GtkWidget *
+egg_simple_popover_new (void)
+{
+  return g_object_new (EGG_TYPE_SIMPLE_POPOVER, NULL);
+}
diff --git a/contrib/egg/egg-simple-popover.h b/contrib/egg/egg-simple-popover.h
new file mode 100644
index 0000000..72be856
--- /dev/null
+++ b/contrib/egg/egg-simple-popover.h
@@ -0,0 +1,90 @@
+/* egg-simple-popover.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef EGG_SIMPLE_POPOVER_H
+#define EGG_SIMPLE_POPOVER_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_SIMPLE_POPOVER (egg_simple_popover_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (EggSimplePopover, egg_simple_popover, EGG, SIMPLE_POPOVER, GtkPopover)
+
+struct _EggSimplePopoverClass
+{
+  GtkPopoverClass parent;
+
+  /**
+   * EggSimplePopover::activate:
+   * @self: The #EggSimplePopover instance.
+   * @text: The text at the time of activation.
+   *
+   * This signal is emitted when the popover's forward button is activated.
+   * Connect to this signal to perform your forward progress.
+   */
+  void (*activate) (EggSimplePopover *self,
+                    const gchar     *text);
+
+  /**
+   * EggSimplePopover::insert-text:
+   * @self: A #EggSimplePopover.
+   * @position: the position in UTF-8 characters.
+   * @chars: the NULL terminated UTF-8 text to insert.
+   * @n_chars: the number of UTF-8 characters in chars.
+   *
+   * Use this signal to determine if text should be allowed to be inserted
+   * into the text buffer. Return GDK_EVENT_STOP to prevent the text from
+   * being inserted.
+   */
+  gboolean (*insert_text) (EggSimplePopover *self,
+                           guint            position,
+                           const gchar     *chars,
+                           guint            n_chars);
+
+  
+  /**
+   * EggSimplePopover::changed:
+   * @self: A #EggSimplePopover.
+   *
+   * This signal is emitted when the entry text changes.
+   */
+  void (*changed) (EggSimplePopover *self);
+};
+
+GtkWidget   *egg_simple_popover_new             (void);
+const gchar *egg_simple_popover_get_text        (EggSimplePopover *self);
+void         egg_simple_popover_set_text        (EggSimplePopover *self,
+                                                const gchar     *text);
+const gchar *egg_simple_popover_get_message     (EggSimplePopover *self);
+void         egg_simple_popover_set_message     (EggSimplePopover *self,
+                                                const gchar     *message);
+const gchar *egg_simple_popover_get_title       (EggSimplePopover *self);
+void         egg_simple_popover_set_title       (EggSimplePopover *self,
+                                                const gchar     *title);
+const gchar *egg_simple_popover_get_button_text (EggSimplePopover *self);
+void         egg_simple_popover_set_button_text (EggSimplePopover *self,
+                                                const gchar     *button_text);
+gboolean     egg_simple_popover_get_ready       (EggSimplePopover *self);
+void         egg_simple_popover_set_ready       (EggSimplePopover *self,
+                                                gboolean         ready);
+
+G_END_DECLS
+
+#endif /* EGG_SIMPLE_POPOVER_H */
diff --git a/data/ui/gb-simple-popover.ui b/contrib/egg/egg-simple-popover.ui
similarity index 96%
rename from data/ui/gb-simple-popover.ui
rename to contrib/egg/egg-simple-popover.ui
index 689efcf..aabadbc 100644
--- a/data/ui/gb-simple-popover.ui
+++ b/contrib/egg/egg-simple-popover.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <!-- interface-requires gtk+ 3.16 -->
-  <template class="GbSimplePopover" parent="GtkPopover">
+  <template class="EggSimplePopover" parent="GtkPopover">
     <child>
       <object class="GtkBox">
         <property name="border-width">12</property>
diff --git a/contrib/egg/egg.gresource.xml b/contrib/egg/egg.gresource.xml
index 41879df..8d1cf87 100644
--- a/contrib/egg/egg.gresource.xml
+++ b/contrib/egg/egg.gresource.xml
@@ -2,5 +2,6 @@
 <gresources>
   <gresource prefix="/org/gnome/libegg-private">
     <file>egg-pill-box.ui</file>
+    <file>egg-simple-popover.ui</file>
   </gresource>
 </gresources>
diff --git a/data/ui/gb-editor-frame.ui b/data/ui/ide-editor-frame.ui
similarity index 98%
rename from data/ui/gb-editor-frame.ui
rename to data/ui/ide-editor-frame.ui
index 7eb4a40..c26aee8 100644
--- a/data/ui/gb-editor-frame.ui
+++ b/data/ui/ide-editor-frame.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <!-- interface-requires gtk+ 3.15 -->
-  <template class="GbEditorFrame" parent="GtkBin">
+  <template class="IdeEditorFrame" parent="GtkBin">
     <child>
       <object class="GtkOverlay" id="frame_overlay">
         <property name="expand">true</property>
@@ -145,7 +145,7 @@
                 <property name="reveal-child">false</property>
                 <property name="transition-type">slide-left</property>
                 <child>
-                  <object class="GbEditorMapBin" id="source_map_container">
+                  <object class="IdeEditorMapBin" id="source_map_container">
                     <property name="floating-bar">floating_bar</property>
                     <property name="visible">true</property>
                   </object>
diff --git a/data/ui/gb-editor-tweak-widget.ui b/data/ui/ide-editor-tweak-widget.ui
similarity index 99%
rename from data/ui/gb-editor-tweak-widget.ui
rename to data/ui/ide-editor-tweak-widget.ui
index 4bfb1af..dc75b5b 100644
--- a/data/ui/gb-editor-tweak-widget.ui
+++ b/data/ui/ide-editor-tweak-widget.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <!-- interface-requires gtk+ 3.8 -->
-  <template class="GbEditorTweakWidget" parent="GtkBin">
+  <template class="IdeEditorTweakWidget" parent="GtkBin">
     <property name="width_request">250</property>
     <property name="height_request">450</property>
     <child>
diff --git a/data/ui/gb-editor-view.ui b/data/ui/ide-editor-view.ui
similarity index 96%
rename from data/ui/gb-editor-view.ui
rename to data/ui/ide-editor-view.ui
index 04f5e00..0bac144 100644
--- a/data/ui/gb-editor-view.ui
+++ b/data/ui/ide-editor-view.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <!-- interface-requires gtk+ 3.15 -->
-  <template class="GbEditorView" parent="GbView">
+  <template class="IdeEditorView" parent="IdeLayoutView">
     <child>
       <object class="GtkBox">
         <property name="orientation">vertical</property>
@@ -73,7 +73,7 @@
                 <property name="orientation">vertical</property>
                 <property name="visible">true</property>
                 <child>
-                  <object class="GbEditorFrame" id="frame1">
+                  <object class="IdeEditorFrame" id="frame1">
                     <property name="visible">true</property>
                   </object>
                   <packing>
@@ -159,13 +159,13 @@
   </template>
   <object class="GtkPopover" id="popover">
     <child>
-      <object class="GbEditorTweakWidget" id="tweak_widget">
+      <object class="IdeEditorTweakWidget" id="tweak_widget">
         <property name="border-width">12</property>
         <property name="visible">true</property>
       </object>
     </child>
   </object>
-  <object class="GbSimplePopover" id="goto_line_popover">
+  <object class="IdeSimplePopover" id="goto_line_popover">
     <property name="visible">true</property>
     <property name="title" translatable="yes">Go to Line</property>
     <property name="button-text" translatable="yes">Go</property>
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 3f04bbc..bc73b5d 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -15,6 +15,10 @@ libide_1_0_la_public_sources = \
        doap/ide-doap.h \
        editor/ide-editor-perspective.c \
        editor/ide-editor-perspective.h \
+       editor/ide-editor-view-addin.c \
+       editor/ide-editor-view-addin.h \
+       editor/ide-editor-view.c \
+       editor/ide-editor-view.h \
        git/ide-git-remote-callbacks.c \
        git/ide-git-remote-callbacks.h \
        git/ide-git-vcs.c \
@@ -227,6 +231,21 @@ libide_1_0_la_SOURCES = \
        editorconfig/editorconfig-glib.h \
        editorconfig/ide-editorconfig-file-settings.c \
        editorconfig/ide-editorconfig-file-settings.h \
+       editor/ide-editor-frame-actions.c \
+       editor/ide-editor-frame-actions.h \
+       editor/ide-editor-frame.c \
+       editor/ide-editor-frame.h \
+       editor/ide-editor-frame-private.h \
+       editor/ide-editor-map-bin.c \
+       editor/ide-editor-map-bin.h \
+       editor/ide-editor-print-operation.c \
+       editor/ide-editor-print-operation.h \
+       editor/ide-editor-tweak-widget.c \
+       editor/ide-editor-tweak-widget.h \
+       editor/ide-editor-view-actions.c \
+       editor/ide-editor-view-actions.h \
+       editor/ide-editor-view-addin-private.h \
+       editor/ide-editor-view-private.h \
        gconstructor.h \
        git/ide-git-buffer-change-monitor.c \
        git/ide-git-buffer-change-monitor.h \
@@ -314,6 +333,8 @@ libide_1_0_la_SOURCES = \
        theatrics/ide-box-theatric.h \
        util/ide-cairo.c \
        util/ide-cairo.h \
+       util/ide-dnd.c \
+       util/ide-dnd.h \
        util/ide-doc-seq.c \
        util/ide-doc-seq.h \
        util/ide-gdk.c \
@@ -341,6 +362,8 @@ libide_1_0_la_includes = \
        -DBUILDDIR=\""${abs_top_builddir}"\" \
        -I$(top_builddir)/libide \
        -I$(top_srcdir)/contrib/egg \
+       -I$(top_srcdir)/contrib/gd \
+       -I$(top_srcdir)/contrib/nautilus \
        -I$(top_srcdir)/contrib/libeditorconfig \
        -I$(top_srcdir)/contrib/search \
        -I$(top_srcdir)/contrib/xml \
@@ -392,7 +415,9 @@ libide_1_0_la_LIBADD = \
        $(SHM_LIB) \
        -lm \
        $(top_builddir)/contrib/egg/libegg-private.la \
+       $(top_builddir)/contrib/gd/libgd.la \
        $(top_builddir)/contrib/libeditorconfig/libeditorconfig.la \
+       $(top_builddir)/contrib/nautilus/libnautilus.la \
        $(top_builddir)/contrib/search/libsearch.la \
        $(top_builddir)/contrib/xml/libxml.la \
        $(NULL)
diff --git a/libide/editor/ide-editor-frame-actions.c b/libide/editor/ide-editor-frame-actions.c
new file mode 100644
index 0000000..6061274
--- /dev/null
+++ b/libide/editor/ide-editor-frame-actions.c
@@ -0,0 +1,117 @@
+/* ide-editor-frame-actions.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <gtksourceview/gtksource.h>
+
+#include "ide-editor-frame-actions.h"
+#include "ide-editor-frame-private.h"
+
+static void
+ide_editor_frame_actions_find (GSimpleAction *action,
+                              GVariant      *variant,
+                              gpointer       user_data)
+{
+  IdeEditorFrame *self = user_data;
+  GtkTextBuffer *buffer;
+  GtkTextIter start_sel;
+  GtkTextIter end_sel;
+  GtkDirectionType search_direction;
+
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+
+  search_direction = (GtkDirectionType) g_variant_get_int32 (variant);
+  ide_source_view_set_search_direction (self->source_view,
+                                        search_direction);
+
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->source_view));
+
+  /*
+   * If the buffer currently has a selection, we prime the search entry with the
+   * selected text. If not, we use our previous search text in the case that it was
+   * cleared by the IdeSourceView internal state.
+   */
+
+  if (gtk_text_buffer_get_has_selection (buffer))
+    {
+      gtk_text_buffer_get_selection_bounds (buffer, &start_sel, &end_sel);
+
+      if (gtk_text_iter_get_line (&start_sel) == gtk_text_iter_get_line (&end_sel))
+        {
+          const gchar *selected_text;
+
+          selected_text = gtk_text_buffer_get_text (buffer, &start_sel, &end_sel, FALSE);
+          gtk_entry_set_text (GTK_ENTRY (self->search_entry), selected_text);
+        }
+    }
+  else if (self->previous_search_string != NULL)
+    {
+      gtk_entry_set_text (GTK_ENTRY (self->search_entry), self->previous_search_string);
+    }
+
+  gtk_revealer_set_reveal_child (self->search_revealer, TRUE);
+  gtk_widget_grab_focus (GTK_WIDGET (self->search_entry));
+}
+
+static void
+ide_editor_frame_actions_next_search_result (GSimpleAction *action,
+                                            GVariant      *variant,
+                                            gpointer       user_data)
+{
+  IdeEditorFrame *self = user_data;
+
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+
+  ide_source_view_set_rubberband_search (self->source_view, FALSE);
+
+  IDE_SOURCE_VIEW_GET_CLASS (self->source_view)->move_search
+    (self->source_view, GTK_DIR_DOWN, FALSE, TRUE, TRUE, FALSE, FALSE);
+}
+
+static void
+ide_editor_frame_actions_previous_search_result (GSimpleAction *action,
+                                                GVariant      *variant,
+                                                gpointer       user_data)
+{
+  IdeEditorFrame *self = user_data;
+
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+
+  ide_source_view_set_rubberband_search (self->source_view, FALSE);
+
+  IDE_SOURCE_VIEW_GET_CLASS (self->source_view)->move_search
+    (self->source_view, GTK_DIR_UP, FALSE, TRUE, TRUE, FALSE, FALSE);
+}
+
+static const GActionEntry IdeEditorFrameActions[] = {
+  { "find", ide_editor_frame_actions_find, "i" },
+  { "next-search-result", ide_editor_frame_actions_next_search_result },
+  { "previous-search-result", ide_editor_frame_actions_previous_search_result },
+};
+
+void
+ide_editor_frame_actions_init (IdeEditorFrame *self)
+{
+  g_autoptr(GSimpleActionGroup) group = NULL;
+
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+
+  group = g_simple_action_group_new ();
+  g_action_map_add_action_entries (G_ACTION_MAP (group), IdeEditorFrameActions,
+                                   G_N_ELEMENTS (IdeEditorFrameActions), self);
+  gtk_widget_insert_action_group (GTK_WIDGET (self), "frame", G_ACTION_GROUP (group));
+}
diff --git a/libide/editor/ide-editor-frame-actions.h b/libide/editor/ide-editor-frame-actions.h
new file mode 100644
index 0000000..48ebfe7
--- /dev/null
+++ b/libide/editor/ide-editor-frame-actions.h
@@ -0,0 +1,30 @@
+/* ide-editor-frame-actions.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_EDITOR_FRAME_ACTIONS_H
+#define IDE_EDITOR_FRAME_ACTIONS_H
+
+#include "ide-editor-frame.h"
+
+G_BEGIN_DECLS
+
+void ide_editor_frame_actions_init (IdeEditorFrame *self);
+
+G_END_DECLS
+
+#endif /* IDE_EDITOR_FRAME_ACTIONS_H */
diff --git a/libide/editor/ide-editor-frame-private.h b/libide/editor/ide-editor-frame-private.h
new file mode 100644
index 0000000..8b3f366
--- /dev/null
+++ b/libide/editor/ide-editor-frame-private.h
@@ -0,0 +1,58 @@
+/* ide-editor-frame-private.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_EDITOR_FRAME_PRIVATE_H
+#define IDE_EDITOR_FRAME_PRIVATE_H
+
+#include <gtk/gtk.h>
+#include <ide.h>
+
+#include "ide-editor-map-bin.h"
+#include "gd-tagged-entry.h"
+#include "nautilus-floating-bar.h"
+
+G_BEGIN_DECLS
+
+struct _IdeEditorFrame
+{
+  GtkBin               parent_instance;
+
+  gchar               *previous_search_string;
+
+  NautilusFloatingBar *floating_bar;
+  GtkRevealer         *map_revealer;
+  GtkLabel            *mode_name_label;
+  GtkLabel            *overwrite_label;
+  GtkScrolledWindow   *scrolled_window;
+  GtkRevealer         *search_revealer;
+  GdTaggedEntry       *search_entry;
+  GdTaggedEntryTag    *search_entry_tag;
+  IdeSourceView       *source_view;
+  IdeEditorMapBin      *source_map_container;
+  IdeSourceMap        *source_map;
+  GtkOverlay          *source_overlay;
+
+  gulong               cursor_moved_handler;
+
+  guint                auto_hide_map : 1;
+  guint                show_ruler : 1;
+};
+
+G_END_DECLS
+
+#endif /* IDE_EDITOR_FRAME_PRIVATE_H */
diff --git a/libide/editor/ide-editor-frame.c b/libide/editor/ide-editor-frame.c
new file mode 100644
index 0000000..3e7013e
--- /dev/null
+++ b/libide/editor/ide-editor-frame.c
@@ -0,0 +1,926 @@
+/* ide-editor-frame.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+
+#include "ide-dnd.h"
+#include "ide-editor-frame-actions.h"
+#include "ide-editor-frame-private.h"
+#include "ide-editor-frame.h"
+#include "ide-editor-map-bin.h"
+#include "ide-gtk.h"
+#include "ide-layout-stack.h"
+
+#define MINIMAP_HIDE_DURATION 1000
+#define MINIMAP_SHOW_DURATION 250
+
+G_DEFINE_TYPE (IdeEditorFrame, ide_editor_frame, GTK_TYPE_BIN)
+
+enum {
+  PROP_0,
+  PROP_AUTO_HIDE_MAP,
+  PROP_BACK_FORWARD_LIST,
+  PROP_DOCUMENT,
+  PROP_SHOW_MAP,
+  PROP_SHOW_RULER,
+  LAST_PROP
+};
+
+enum {
+  TARGET_URI_LIST = 100
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+ide_editor_frame_update_ruler (IdeEditorFrame *self)
+{
+  const gchar *mode_display_name;
+  const gchar *mode_name;
+  GtkTextBuffer *buffer;
+  gboolean visible = FALSE;
+
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->source_view));
+
+  if (!IDE_IS_BUFFER (buffer))
+    return;
+
+  /* update line/column text */
+  if (self->show_ruler)
+    {
+      g_autofree gchar *text = NULL;
+      guint ln = 0;
+      guint col = 0;
+
+      ide_source_view_get_visual_position (self->source_view, &ln, &col);
+      text = g_strdup_printf (_("Line %u, Column %u"), ln + 1, col + 1);
+      nautilus_floating_bar_set_primary_label (self->floating_bar, text);
+
+      visible = TRUE;
+    }
+  else
+    {
+      nautilus_floating_bar_set_primary_label (self->floating_bar, NULL);
+    }
+
+  /* update current mode */
+  mode_display_name = ide_source_view_get_mode_display_name (self->source_view);
+  gtk_label_set_label (self->mode_name_label, mode_display_name);
+  gtk_widget_set_visible (GTK_WIDGET (self->mode_name_label), !!mode_display_name);
+  if (mode_display_name != NULL)
+    visible = TRUE;
+
+  /*
+   * Update overwrite label.
+   *
+   * XXX: Hack for 3.18 to ensure we don't have "OVR Replace" in vim mode.
+   */
+  mode_name = ide_source_view_get_mode_name (self->source_view);
+  if (ide_source_view_get_overwrite (self->source_view) &&
+      !ide_str_equal0 (mode_name, "vim-replace"))
+    {
+      gtk_widget_set_visible (GTK_WIDGET (self->overwrite_label), TRUE);
+      visible = TRUE;
+    }
+  else
+    {
+      gtk_widget_set_visible (GTK_WIDGET (self->overwrite_label), FALSE);
+    }
+
+  if (gtk_widget_get_visible (GTK_WIDGET (self->mode_name_label)))
+    visible = TRUE;
+
+  if (ide_buffer_get_busy (IDE_BUFFER (buffer)))
+    {
+      nautilus_floating_bar_set_show_spinner (self->floating_bar, TRUE);
+      visible = TRUE;
+    }
+  else
+    {
+      nautilus_floating_bar_set_show_spinner (self->floating_bar, FALSE);
+    }
+
+  /* we don't fade while hiding because we likely won't have
+   * any text labels set anyway.
+   */
+  if (!visible && gtk_widget_get_visible (GTK_WIDGET (self->floating_bar)))
+    gtk_widget_hide (GTK_WIDGET (self->floating_bar));
+  else if (visible && !gtk_widget_get_visible (GTK_WIDGET (self->floating_bar)))
+    gtk_widget_show (GTK_WIDGET (self->floating_bar));
+}
+
+static void
+ide_editor_frame_set_show_ruler (IdeEditorFrame *self,
+                                gboolean       show_ruler)
+{
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+
+  if (show_ruler != self->show_ruler)
+    {
+      self->show_ruler = show_ruler;
+      ide_editor_frame_update_ruler (self);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SHOW_RULER]);
+    }
+}
+
+static void
+ide_editor_frame_animate_map (IdeEditorFrame *self,
+                             gboolean       visible)
+{
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+
+  gtk_revealer_set_reveal_child (self->map_revealer, visible);
+}
+
+static void
+ide_editor_frame_show_map (IdeEditorFrame *self,
+                          IdeSourceMap  *source_map)
+{
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+  g_assert (IDE_IS_SOURCE_MAP (source_map));
+
+  ide_editor_frame_animate_map (self, TRUE);
+}
+
+static void
+ide_editor_frame_hide_map (IdeEditorFrame *self,
+                          IdeSourceMap  *source_map)
+{
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+  g_assert (IDE_IS_SOURCE_MAP (source_map));
+
+  /* ignore hide request if auto-hide is disabled */
+  if ((self->source_map != NULL) && !self->auto_hide_map)
+    return;
+
+  ide_editor_frame_animate_map (self, FALSE);
+}
+
+static void
+ide_editor_frame_set_position_label (IdeEditorFrame *self,
+                                    const gchar   *text)
+{
+  g_return_if_fail (IDE_IS_EDITOR_FRAME (self));
+
+  if (!text || !*text)
+    {
+      if (self->search_entry_tag)
+        {
+          gd_tagged_entry_remove_tag (self->search_entry, self->search_entry_tag);
+          g_clear_object (&self->search_entry_tag);
+        }
+      return;
+    }
+
+  if (!self->search_entry_tag)
+    {
+      self->search_entry_tag = gd_tagged_entry_tag_new ("");
+      gd_tagged_entry_tag_set_style (self->search_entry_tag, "gb-search-entry-occurrences-tag");
+      gd_tagged_entry_add_tag (self->search_entry, self->search_entry_tag);
+    }
+
+  gd_tagged_entry_tag_set_label (self->search_entry_tag, text);
+}
+
+static void
+ide_editor_frame_update_search_position_label (IdeEditorFrame *self)
+{
+  GtkSourceSearchContext *search_context;
+  GtkStyleContext *context;
+  GtkTextBuffer *buffer;
+  GtkTextIter begin;
+  GtkTextIter end;
+  const gchar *search_text;
+  gchar *text;
+  gint count;
+  gint pos;
+
+  g_return_if_fail (IDE_IS_EDITOR_FRAME (self));
+
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->source_view));
+  search_context = ide_source_view_get_search_context (self->source_view);
+  gtk_text_buffer_get_selection_bounds (buffer, &begin, &end);
+  pos = gtk_source_search_context_get_occurrence_position (search_context, &begin, &end);
+  count = gtk_source_search_context_get_occurrences_count (search_context);
+
+  if ((pos == -1) || (count == -1))
+    {
+      /*
+       * We are not yet done scanning the buffer.
+       * We will be updated when we know more, so just hide it for now.
+       */
+      ide_editor_frame_set_position_label (self, NULL);
+      return;
+    }
+
+  context = gtk_widget_get_style_context (GTK_WIDGET (self->search_entry));
+  search_text = gtk_entry_get_text (GTK_ENTRY (self->search_entry));
+
+  if ((count == 0) && !ide_str_empty0 (search_text))
+    gtk_style_context_add_class (context, GTK_STYLE_CLASS_ERROR);
+  else
+    gtk_style_context_remove_class (context, GTK_STYLE_CLASS_ERROR);
+
+  text = g_strdup_printf (_("%u of %u"), pos, count);
+  ide_editor_frame_set_position_label (self, text);
+  g_free (text);
+}
+
+static void
+ide_editor_frame_on_search_occurrences_notify (IdeEditorFrame          *self,
+                                              GParamSpec             *pspec,
+                                              GtkSourceSearchContext *search_context)
+{
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+  g_assert (GTK_SOURCE_IS_SEARCH_CONTEXT (search_context));
+
+  ide_editor_frame_update_search_position_label (self);
+}
+
+static void
+on_cursor_moved (IdeBuffer         *buffer,
+                 const GtkTextIter *location,
+                 IdeEditorFrame    *self)
+{
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+  g_assert (IDE_IS_BUFFER (buffer));
+
+  ide_editor_frame_update_ruler (self);
+  ide_editor_frame_update_search_position_label (self);
+}
+
+/**
+ * ide_editor_frame_get_document:
+ *
+ * Gets the #IdeEditorFrame:document property.
+ *
+ * Returns: (transfer none) (nullable): An #IdeBuffer or %NULL.
+ */
+IdeBuffer *
+ide_editor_frame_get_document (IdeEditorFrame *self)
+{
+  GtkTextBuffer *buffer;
+
+  g_return_val_if_fail (IDE_IS_EDITOR_FRAME (self), NULL);
+
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->source_view));
+
+  return IDE_IS_BUFFER (buffer) ? IDE_BUFFER (buffer) : NULL;
+}
+
+static gboolean
+search_text_transform_to (GBinding     *binding,
+                          const GValue *from_value,
+                          GValue       *to_value,
+                          gpointer      user_data)
+{
+  g_assert (from_value != NULL);
+  g_assert (to_value != NULL);
+
+  if (g_value_get_string (from_value) == NULL)
+    g_value_set_string (to_value, "");
+  else
+    g_value_copy (from_value, to_value);
+
+  return TRUE;
+}
+
+static gboolean
+search_text_transform_from (GBinding     *binding,
+                            const GValue *from_value,
+                            GValue       *to_value,
+                            gpointer      user_data)
+{
+  g_assert (from_value != NULL);
+  g_assert (to_value != NULL);
+
+  if (g_value_get_string (from_value) == NULL)
+    g_value_set_string (to_value, "");
+  else
+    g_value_copy (from_value, to_value);
+
+  return TRUE;
+}
+
+void
+ide_editor_frame_set_document (IdeEditorFrame *self,
+                               IdeBuffer      *buffer)
+{
+  GtkSourceSearchContext *search_context;
+  GtkSourceSearchSettings *search_settings;
+  GtkTextMark *mark;
+  GtkTextIter iter;
+
+  g_return_if_fail (IDE_IS_EDITOR_FRAME (self));
+  g_return_if_fail (IDE_IS_BUFFER (buffer));
+
+  gtk_text_view_set_buffer (GTK_TEXT_VIEW (self->source_view), GTK_TEXT_BUFFER (buffer));
+
+  g_signal_connect_object (buffer,
+                           "notify::busy",
+                           G_CALLBACK (ide_editor_frame_update_ruler),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  self->cursor_moved_handler =
+    g_signal_connect (buffer,
+                      "cursor-moved",
+                      G_CALLBACK (on_cursor_moved),
+                      self);
+  mark = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (buffer));
+  gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer), &iter, mark);
+  on_cursor_moved (buffer, &iter, self);
+
+  /*
+   * Sync search entry with the search settings.
+   */
+  search_context = ide_source_view_get_search_context (self->source_view);
+  search_settings = gtk_source_search_context_get_settings (search_context);
+  g_object_bind_property_full (self->search_entry, "text", search_settings, "search-text",
+                               (G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL),
+                               search_text_transform_to, search_text_transform_from,
+                               NULL, NULL);
+  g_signal_connect_object (search_context,
+                           "notify::occurrences-count",
+                           G_CALLBACK (ide_editor_frame_on_search_occurrences_notify),
+                           self,
+                           G_CONNECT_SWAPPED);
+}
+
+static gboolean
+get_smart_home_end (GValue   *value,
+                    GVariant *variant,
+                    gpointer  user_data)
+{
+  if (g_variant_get_boolean (variant))
+    g_value_set_enum (value, GTK_SOURCE_SMART_HOME_END_BEFORE);
+  else
+    g_value_set_enum (value, GTK_SOURCE_SMART_HOME_END_DISABLED);
+
+  return TRUE;
+}
+
+static void
+keybindings_changed (GSettings     *settings,
+                     const gchar   *key,
+                     IdeEditorFrame *self)
+{
+  g_signal_emit_by_name (self->source_view,
+                         "set-mode",
+                         NULL,
+                         IDE_SOURCE_VIEW_MODE_TYPE_PERMANENT);
+}
+
+static void
+ide_editor_frame_grab_focus (GtkWidget *widget)
+{
+  IdeEditorFrame *self = (IdeEditorFrame *)widget;
+
+  gtk_widget_grab_focus (GTK_WIDGET (self->source_view));
+}
+
+static void
+ide_editor_frame__drag_data_received (IdeEditorFrame    *self,
+                                     GdkDragContext   *context,
+                                     gint              x,
+                                     gint              y,
+                                     GtkSelectionData *selection_data,
+                                     guint             info,
+                                     guint             timestamp,
+                                     GtkWidget        *widget)
+{
+  gchar **uri_list;
+
+  g_return_if_fail (IDE_IS_SOURCE_VIEW (widget));
+
+  switch (info)
+    {
+    case TARGET_URI_LIST:
+      uri_list = ide_dnd_get_uri_list (selection_data);
+
+      if (uri_list)
+        {
+          GVariantBuilder *builder;
+          GVariant *variant;
+          guint i;
+
+          builder = g_variant_builder_new (G_VARIANT_TYPE_STRING_ARRAY);
+          for (i = 0; uri_list [i]; i++)
+            g_variant_builder_add (builder, "s", uri_list [i]);
+          variant = g_variant_builder_end (builder);
+          g_variant_builder_unref (builder);
+          g_strfreev (uri_list);
+
+          /*
+           * request that we get focus first so the workbench will deliver the
+           * document to us in the case it is not already open
+           */
+          gtk_widget_grab_focus (GTK_WIDGET (self));
+
+          ide_widget_action (GTK_WIDGET (self), "workbench", "open-uri-list", variant);
+        }
+
+      gtk_drag_finish (context, TRUE, FALSE, timestamp);
+      break;
+
+    default:
+      break;
+    }
+}
+
+static gboolean
+ide_editor_frame__search_key_press_event (IdeEditorFrame *self,
+                                         GdkEventKey   *event,
+                                         GdTaggedEntry *entry)
+{
+  GtkTextBuffer *buffer;
+
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+  g_assert (GD_IS_TAGGED_ENTRY (entry));
+
+  switch (event->keyval)
+    {
+    case GDK_KEY_Escape:
+      /* stash the search string for later */
+      g_free (self->previous_search_string);
+      g_object_get (self->search_entry, "text", &self->previous_search_string, NULL);
+
+      /* clear the highlights in the source view */
+      ide_source_view_clear_search (self->source_view);
+
+      /* disable rubberbanding and ensure insert mark is on screen */
+      buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->source_view));
+      ide_source_view_set_rubberband_search (self->source_view, FALSE);
+      ide_source_view_scroll_mark_onscreen (self->source_view,
+                                            gtk_text_buffer_get_insert (buffer),
+                                            TRUE,
+                                            0.5,
+                                            0.5);
+
+      /* finally we can focus the source view */
+      gtk_widget_grab_focus (GTK_WIDGET (self->source_view));
+
+      return GDK_EVENT_STOP;
+
+    case GDK_KEY_KP_Enter:
+    case GDK_KEY_Return:
+      ide_widget_action (GTK_WIDGET (self), "frame", "next-search-result", NULL);
+      gtk_widget_grab_focus (GTK_WIDGET (self->source_view));
+      return GDK_EVENT_STOP;
+
+    case GDK_KEY_Down:
+      ide_widget_action (GTK_WIDGET (self), "frame", "next-search-result", NULL);
+      return GDK_EVENT_STOP;
+
+    case GDK_KEY_Up:
+      ide_widget_action (GTK_WIDGET (self), "frame", "previous-search-result", NULL);
+      return GDK_EVENT_STOP;
+
+    default:
+      {
+        GtkSourceSearchSettings *search_settings;
+        GtkSourceSearchContext *search_context;
+
+        if (!ide_source_view_get_rubberband_search (self->source_view))
+          ide_source_view_set_rubberband_search (self->source_view, TRUE);
+
+        /*
+         * Other modes, such as Vim emulation, want word boundaries, but we do
+         * not when searching from this entry. Sort of hacky, but gets the job
+         * done to just change that setting here.
+         */
+        search_context = ide_source_view_get_search_context (self->source_view);
+        search_settings = gtk_source_search_context_get_settings (search_context);
+        gtk_source_search_settings_set_at_word_boundaries (search_settings, FALSE);
+      }
+      break;
+    }
+
+  return GDK_EVENT_PROPAGATE;
+}
+
+static gboolean
+ide_editor_frame__source_view_focus_in_event (IdeEditorFrame *self,
+                                             GdkEventKey   *event,
+                                             IdeSourceView *source_view)
+{
+  GtkTextBuffer *buffer;
+
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+  g_assert (IDE_IS_SOURCE_VIEW (source_view));
+
+  gtk_revealer_set_reveal_child (self->search_revealer, FALSE);
+
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (source_view));
+
+  if (IDE_IS_BUFFER (buffer))
+    ide_buffer_check_for_volume_change (IDE_BUFFER (buffer));
+
+  return FALSE;
+}
+
+static void
+ide_editor_frame__source_view_focus_location (IdeEditorFrame    *self,
+                                              IdeSourceLocation *location,
+                                              IdeSourceView     *source_view)
+{
+  GtkWidget *toplevel;
+
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+  g_assert (location != NULL);
+  g_assert (IDE_IS_SOURCE_VIEW (source_view));
+
+  toplevel = gtk_widget_get_ancestor (GTK_WIDGET (self), IDE_TYPE_WORKBENCH);
+
+  if (IDE_IS_WORKBENCH (toplevel))
+    {
+      /*
+       * Convert location to Uri and open.
+       */
+#if 0
+      ide_layout_stack_focus_location (IDE_LAYOUT_STACK (widget), location);
+#endif
+    }
+}
+
+static void
+ide_editor_frame__source_view_request_documentation (IdeEditorFrame *self,
+                                                     IdeSourceView  *source_view)
+{
+  GtkTextBuffer *buffer;
+  GtkTextIter begin;
+  GtkTextIter end;
+  GVariant *param;
+  g_autofree gchar *text = NULL;
+
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+  g_assert (IDE_IS_SOURCE_VIEW (source_view));
+
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (source_view));
+  gtk_text_buffer_get_selection_bounds (buffer, &begin, &end);
+  text = gtk_text_iter_get_slice (&begin, &end);
+
+  param = g_variant_new_string (text);
+  ide_widget_action (GTK_WIDGET (self), "workbench", "search-docs", param);
+}
+
+static gboolean
+ide_editor_frame_get_show_map (IdeEditorFrame *self)
+{
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+
+  return (self->source_map != NULL);
+}
+
+static void
+ide_editor_frame_set_show_map (IdeEditorFrame *self,
+                              gboolean       show_map)
+{
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+
+  if (show_map != ide_editor_frame_get_show_map (self))
+    {
+      if (self->source_map != NULL)
+        {
+          gtk_container_remove (GTK_CONTAINER (self->source_map_container),
+                                GTK_WIDGET (self->source_map));
+          self->source_map = NULL;
+        }
+      else
+        {
+          self->source_map = g_object_new (IDE_TYPE_SOURCE_MAP,
+                                           "view", self->source_view,
+                                           "visible", TRUE,
+                                           NULL);
+          g_signal_connect_object (self->source_map,
+                                   "show-map",
+                                   G_CALLBACK (ide_editor_frame_show_map),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+          g_signal_connect_object (self->source_map,
+                                   "hide-map",
+                                   G_CALLBACK (ide_editor_frame_hide_map),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+          gtk_container_add (GTK_CONTAINER (self->source_map_container),
+                             GTK_WIDGET (self->source_map));
+          g_signal_emit_by_name (self->source_map, "show-map");
+        }
+
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SHOW_MAP]);
+    }
+}
+
+static void
+ide_editor_frame_set_auto_hide_map (IdeEditorFrame *self,
+                                   gboolean       auto_hide_map)
+{
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+
+  auto_hide_map = !!auto_hide_map;
+
+  if (auto_hide_map != self->auto_hide_map)
+    {
+      self->auto_hide_map = auto_hide_map;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_AUTO_HIDE_MAP]);
+    }
+}
+
+static void
+ide_editor_frame__source_view_populate_popup (IdeEditorFrame *self,
+                                             GtkWidget     *popup,
+                                             IdeSourceView *source_view)
+{
+  g_assert (IDE_IS_EDITOR_FRAME (self));
+  g_assert (GTK_IS_WIDGET (popup));
+  g_assert (IDE_IS_SOURCE_VIEW (source_view));
+
+  if (GTK_IS_MENU_SHELL (popup))
+    {
+      GtkWidget *item;
+      GtkWidget *sep;
+
+      sep = g_object_new (GTK_TYPE_SEPARATOR_MENU_ITEM,
+                          "visible", TRUE,
+                          NULL);
+      gtk_menu_shell_append (GTK_MENU_SHELL (popup), sep);
+
+      item = g_object_new (GTK_TYPE_MENU_ITEM,
+                           "action-name", "view.reveal",
+                           "label", _("Re_veal in Project Tree"),
+                           "use-underline", TRUE,
+                           "visible", TRUE,
+                           NULL);
+      gtk_menu_shell_append (GTK_MENU_SHELL (popup), item);
+    }
+}
+
+static void
+ide_editor_frame_constructed (GObject *object)
+{
+  IdeEditorFrame *self = (IdeEditorFrame *)object;
+
+  G_OBJECT_CLASS (ide_editor_frame_parent_class)->constructed (object);
+
+  g_signal_connect_object (self->source_view,
+                           "drag-data-received",
+                           G_CALLBACK (ide_editor_frame__drag_data_received),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->source_view,
+                           "focus-in-event",
+                           G_CALLBACK (ide_editor_frame__source_view_focus_in_event),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->source_view,
+                           "focus-location",
+                           G_CALLBACK (ide_editor_frame__source_view_focus_location),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->source_view,
+                           "populate-popup",
+                           G_CALLBACK (ide_editor_frame__source_view_populate_popup),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->source_view,
+                           "request-documentation",
+                           G_CALLBACK (ide_editor_frame__source_view_request_documentation),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->search_entry,
+                           "key-press-event",
+                           G_CALLBACK (ide_editor_frame__search_key_press_event),
+                           self,
+                           G_CONNECT_SWAPPED);
+}
+
+static void
+ide_editor_frame_dispose (GObject *object)
+{
+  IdeEditorFrame *self = (IdeEditorFrame *)object;
+
+  g_clear_pointer (&self->previous_search_string, g_free);
+
+  if (self->source_view && self->cursor_moved_handler)
+    {
+      GtkTextBuffer *buffer;
+
+      buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->source_view));
+      ide_clear_signal_handler (buffer, &self->cursor_moved_handler);
+    }
+
+  g_clear_object (&self->search_entry_tag);
+
+  G_OBJECT_CLASS (ide_editor_frame_parent_class)->dispose (object);
+}
+
+static void
+ide_editor_frame_get_property (GObject    *object,
+                              guint       prop_id,
+                              GValue     *value,
+                              GParamSpec *pspec)
+{
+  IdeEditorFrame *self = IDE_EDITOR_FRAME (object);
+
+  switch (prop_id)
+    {
+    case PROP_AUTO_HIDE_MAP:
+      g_value_set_boolean (value, self->auto_hide_map);
+      break;
+
+    case PROP_DOCUMENT:
+      g_value_set_object (value, ide_editor_frame_get_document (self));
+      break;
+
+    case PROP_SHOW_MAP:
+      g_value_set_boolean (value, ide_editor_frame_get_show_map (self));
+      break;
+
+    case PROP_SHOW_RULER:
+      g_value_set_boolean (value, self->show_ruler);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_editor_frame_set_property (GObject      *object,
+                              guint         prop_id,
+                              const GValue *value,
+                              GParamSpec   *pspec)
+{
+  IdeEditorFrame *self = IDE_EDITOR_FRAME (object);
+
+  switch (prop_id)
+    {
+    case PROP_AUTO_HIDE_MAP:
+      ide_editor_frame_set_auto_hide_map (self, g_value_get_boolean (value));
+      break;
+
+    case PROP_DOCUMENT:
+      ide_editor_frame_set_document (self, g_value_get_object (value));
+      break;
+
+    case PROP_BACK_FORWARD_LIST:
+      ide_source_view_set_back_forward_list (self->source_view, g_value_get_object (value));
+      break;
+
+    case PROP_SHOW_MAP:
+      ide_editor_frame_set_show_map (self, g_value_get_boolean (value));
+      break;
+
+    case PROP_SHOW_RULER:
+      ide_editor_frame_set_show_ruler (self, g_value_get_boolean (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_editor_frame_class_init (IdeEditorFrameClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->constructed = ide_editor_frame_constructed;
+  object_class->dispose = ide_editor_frame_dispose;
+  object_class->get_property = ide_editor_frame_get_property;
+  object_class->set_property = ide_editor_frame_set_property;
+
+  widget_class->grab_focus = ide_editor_frame_grab_focus;
+
+  properties [PROP_AUTO_HIDE_MAP] =
+    g_param_spec_boolean ("auto-hide-map",
+                          "Auto Hide Map",
+                          "Auto Hide Map",
+                          FALSE,
+                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_BACK_FORWARD_LIST] =
+    g_param_spec_object ("back-forward-list",
+                         "Back Forward List",
+                         "The back forward list.",
+                         IDE_TYPE_BACK_FORWARD_LIST,
+                         (G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_DOCUMENT] =
+    g_param_spec_object ("document",
+                         "Document",
+                         "The editor document.",
+                         IDE_TYPE_BUFFER,
+                         (G_PARAM_READWRITE | 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_STATIC_STRINGS));
+
+  properties [PROP_SHOW_RULER] =
+    g_param_spec_boolean ("show-ruler",
+                          "Show Ruler",
+                          "If the ruler should always be shown.",
+                          FALSE,
+                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/builder/ui/ide-editor-frame.ui");
+
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, floating_bar);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, map_revealer);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, mode_name_label);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, overwrite_label);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, scrolled_window);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, search_entry);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, search_revealer);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, source_map_container);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, source_overlay);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, source_view);
+
+  g_type_ensure (NAUTILUS_TYPE_FLOATING_BAR);
+  g_type_ensure (GD_TYPE_TAGGED_ENTRY);
+}
+
+static void
+ide_editor_frame_init (IdeEditorFrame *self)
+{
+  g_autoptr(GSettings) settings = NULL;
+  g_autoptr(GSettings) insight_settings = NULL;
+  GtkTargetList *target_list;
+
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  ide_editor_frame_actions_init (self);
+
+  settings = g_settings_new ("org.gnome.builder.editor");
+  g_settings_bind (settings, "draw-spaces", self->source_view, "draw-spaces", G_SETTINGS_BIND_DEFAULT);
+  g_settings_bind (settings, "font-name", self->source_view, "font-name", G_SETTINGS_BIND_GET);
+  g_settings_bind (settings, "highlight-current-line", self->source_view, "highlight-current-line", 
G_SETTINGS_BIND_GET);
+  g_settings_bind (settings, "scroll-offset", self->source_view, "scroll-offset", G_SETTINGS_BIND_GET);
+  g_settings_bind (settings, "show-grid-lines", self->source_view, "show-grid-lines", G_SETTINGS_BIND_GET);
+  g_settings_bind (settings, "show-line-changes", self->source_view, "show-line-changes", 
G_SETTINGS_BIND_GET);
+  g_settings_bind (settings, "show-line-numbers", self->source_view, "show-line-numbers", 
G_SETTINGS_BIND_GET);
+  g_settings_bind (settings, "smart-backspace", self->source_view, "smart-backspace", G_SETTINGS_BIND_GET);
+  g_settings_bind_with_mapping (settings, "smart-home-end", self->source_view, "smart-home-end", 
G_SETTINGS_BIND_GET, get_smart_home_end, NULL, NULL, NULL);
+  g_settings_bind (settings, "show-map", self, "show-map", G_SETTINGS_BIND_GET);
+  g_settings_bind (settings, "auto-hide-map", self, "auto-hide-map", G_SETTINGS_BIND_GET);
+  g_signal_connect (settings, "changed::keybindings", G_CALLBACK (keybindings_changed), self);
+
+  insight_settings = g_settings_new ("org.gnome.builder.code-insight");
+  g_settings_bind (insight_settings, "word-completion", self->source_view, "enable-word-completion", 
G_SETTINGS_BIND_GET);
+
+  g_signal_connect_object (self->source_view,
+                           "notify::overwrite",
+                           G_CALLBACK (ide_editor_frame_update_ruler),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->source_view,
+                           "notify::mode-display-name",
+                           G_CALLBACK (ide_editor_frame_update_ruler),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  /*
+   * we want to rubberbanding search until enter has been pressed or next/previous actions
+   * have been activated.
+   */
+  g_object_bind_property (self->search_revealer, "visible",
+                          self->source_view, "rubberband-search",
+                          G_BINDING_SYNC_CREATE);
+
+  /*
+   * Drag and drop support
+   */
+  target_list = gtk_drag_dest_get_target_list (GTK_WIDGET (self->source_view));
+  if (target_list)
+    gtk_target_list_add_uri_targets (target_list, TARGET_URI_LIST);
+}
diff --git a/libide/editor/ide-editor-frame.h b/libide/editor/ide-editor-frame.h
new file mode 100644
index 0000000..5b3c7c4
--- /dev/null
+++ b/libide/editor/ide-editor-frame.h
@@ -0,0 +1,38 @@
+/* ide-editor-frame.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_EDITOR_FRAME_H
+#define IDE_EDITOR_FRAME_H
+
+#include <gtk/gtk.h>
+
+#include "ide-buffer.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_EDITOR_FRAME (ide_editor_frame_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeEditorFrame, ide_editor_frame, IDE, EDITOR_FRAME, GtkBin)
+
+IdeBuffer *ide_editor_frame_get_document (IdeEditorFrame *self);
+void       ide_editor_frame_set_document (IdeEditorFrame *self,
+                                          IdeBuffer      *buffer);
+
+G_END_DECLS
+
+#endif /* IDE_EDITOR_FRAME_H */
diff --git a/libide/editor/ide-editor-map-bin.c b/libide/editor/ide-editor-map-bin.c
new file mode 100644
index 0000000..eaabeb8
--- /dev/null
+++ b/libide/editor/ide-editor-map-bin.c
@@ -0,0 +1,245 @@
+/* ide-editor-map-bin.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+#include <ide.h>
+#include <pango/pangofc-fontmap.h>
+
+#include "ide-editor-map-bin.h"
+
+struct _IdeEditorMapBin
+{
+  GtkBox     parent_instance;
+  gint       cached_height;
+  gulong     size_allocate_handler;
+  GtkWidget *floating_bar;
+  GtkWidget *separator;
+};
+
+G_DEFINE_TYPE (IdeEditorMapBin, ide_editor_map_bin, GTK_TYPE_BOX)
+
+enum {
+  PROP_0,
+  PROP_FLOATING_BAR,
+  LAST_PROP
+};
+
+static FcConfig *localFontConfig;
+static GParamSpec *properties [LAST_PROP];
+
+static void
+ide_editor_map_bin__floating_bar_size_allocate (IdeEditorMapBin *self,
+                                               GtkAllocation  *alloc,
+                                               GtkWidget      *floating_bar)
+{
+  g_assert (IDE_IS_EDITOR_MAP_BIN (self));
+  g_assert (alloc != NULL);
+  g_assert (GTK_IS_WIDGET (floating_bar));
+
+  if (self->cached_height != alloc->height)
+    {
+      self->cached_height = alloc->height;
+      gtk_widget_queue_resize (GTK_WIDGET (self));
+    }
+}
+
+static void
+ide_editor_map_bin_set_floating_bar (IdeEditorMapBin *self,
+                                    GtkWidget      *floating_bar)
+{
+  g_return_if_fail (IDE_IS_EDITOR_MAP_BIN (self));
+
+  if (floating_bar != self->floating_bar)
+    {
+      self->cached_height = 0;
+
+      if (self->floating_bar)
+        {
+          ide_clear_signal_handler (self->floating_bar, &self->size_allocate_handler);
+          ide_clear_weak_pointer (&self->floating_bar);
+        }
+
+      if (floating_bar)
+        {
+          ide_set_weak_pointer (&self->floating_bar, floating_bar);
+          g_signal_connect_object (self->floating_bar,
+                                   "size-allocate",
+                                   G_CALLBACK (ide_editor_map_bin__floating_bar_size_allocate),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+          gtk_widget_queue_resize (GTK_WIDGET (floating_bar));
+        }
+
+      gtk_widget_queue_resize (GTK_WIDGET (self));
+    }
+}
+
+static void
+ide_editor_map_bin_size_allocate (GtkWidget     *widget,
+                                 GtkAllocation *alloc)
+{
+  IdeEditorMapBin *self = (IdeEditorMapBin *)widget;
+
+  if (self->floating_bar != NULL)
+    alloc->height -= self->cached_height;
+
+  GTK_WIDGET_CLASS (ide_editor_map_bin_parent_class)->size_allocate (widget, alloc);
+}
+
+static void
+ide_editor_map_bin_add (GtkContainer *container,
+                       GtkWidget    *child)
+{
+  IdeEditorMapBin *self = (IdeEditorMapBin *)container;
+
+  if (IDE_IS_SOURCE_MAP (child) && (self->separator != NULL))
+    {
+      PangoFontMap *font_map;
+      PangoFontDescription *font_desc;
+
+      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 (child, font_map);
+
+      font_desc = pango_font_description_from_string ("Builder Blocks 1");
+      g_object_set (child, "font-desc", font_desc, NULL);
+
+      g_object_unref (font_map);
+      pango_font_description_free (font_desc);
+
+      gtk_widget_show (GTK_WIDGET (self->separator));
+    }
+
+  GTK_CONTAINER_CLASS (ide_editor_map_bin_parent_class)->add (container, child);
+}
+
+static void
+ide_editor_map_bin_remove (GtkContainer *container,
+                          GtkWidget    *child)
+{
+  IdeEditorMapBin *self = (IdeEditorMapBin *)container;
+
+  if (IDE_IS_SOURCE_MAP (child) && (self->separator != NULL))
+    gtk_widget_hide (GTK_WIDGET (self->separator));
+
+  GTK_CONTAINER_CLASS (ide_editor_map_bin_parent_class)->remove (container, child);
+}
+
+static void
+ide_editor_map_bin_finalize (GObject *object)
+{
+  IdeEditorMapBin *self = (IdeEditorMapBin *)object;
+
+  if (self->separator != NULL)
+    g_object_remove_weak_pointer (G_OBJECT (self->separator), (gpointer *)&self->separator);
+  ide_clear_signal_handler (self->floating_bar, &self->size_allocate_handler);
+  ide_clear_weak_pointer (&self->floating_bar);
+
+  G_OBJECT_CLASS (ide_editor_map_bin_parent_class)->finalize (object);
+}
+
+static void
+ide_editor_map_bin_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  IdeEditorMapBin *self = IDE_EDITOR_MAP_BIN (object);
+
+  switch (prop_id)
+    {
+    case PROP_FLOATING_BAR:
+      g_value_set_object (value, self->floating_bar);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_editor_map_bin_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  IdeEditorMapBin *self = IDE_EDITOR_MAP_BIN (object);
+
+  switch (prop_id)
+    {
+    case PROP_FLOATING_BAR:
+      ide_editor_map_bin_set_floating_bar (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_editor_map_bin_load_font (void)
+{
+  const gchar *font_path = PACKAGE_DATADIR"/gnome-builder/fonts/BuilderBlocks.ttf";
+
+  localFontConfig = FcInitLoadConfigAndFonts ();
+
+  if (g_getenv ("GB_IN_TREE_FONTS") != NULL)
+    font_path = "data/fonts/BuilderBlocks.ttf";
+
+  FcConfigAppFontAddFile (localFontConfig, (const FcChar8 *)font_path);
+}
+
+static void
+ide_editor_map_bin_class_init (IdeEditorMapBinClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+  object_class->finalize = ide_editor_map_bin_finalize;
+  object_class->get_property = ide_editor_map_bin_get_property;
+  object_class->set_property = ide_editor_map_bin_set_property;
+
+  widget_class->size_allocate = ide_editor_map_bin_size_allocate;
+
+  container_class->add = ide_editor_map_bin_add;
+  container_class->remove = ide_editor_map_bin_remove;
+
+  properties [PROP_FLOATING_BAR] =
+    g_param_spec_object ("floating-bar",
+                         "Floating Bar",
+                         "The floating bar to use for relative allocation size.",
+                         GTK_TYPE_WIDGET,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+  ide_editor_map_bin_load_font ();
+}
+
+static void
+ide_editor_map_bin_init (IdeEditorMapBin *self)
+{
+  self->separator = g_object_new (GTK_TYPE_SEPARATOR,
+                                  "orientation", GTK_ORIENTATION_VERTICAL,
+                                  "hexpand", FALSE,
+                                  "visible", FALSE,
+                                  NULL);
+  g_object_add_weak_pointer (G_OBJECT (self->separator), (gpointer *)&self->separator);
+  gtk_container_add (GTK_CONTAINER (self), self->separator);
+}
diff --git a/libide/editor/ide-editor-map-bin.h b/libide/editor/ide-editor-map-bin.h
new file mode 100644
index 0000000..84a537a
--- /dev/null
+++ b/libide/editor/ide-editor-map-bin.h
@@ -0,0 +1,32 @@
+/* ide-editor-map-bin.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_EDITOR_MAP_BIN_H
+#define IDE_EDITOR_MAP_BIN_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_EDITOR_MAP_BIN (ide_editor_map_bin_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeEditorMapBin, ide_editor_map_bin, IDE, EDITOR_MAP_BIN, GtkBox)
+
+G_END_DECLS
+
+#endif /* IDE_EDITOR_MAP_BIN_H */
diff --git a/libide/editor/ide-editor-print-operation.c b/libide/editor/ide-editor-print-operation.c
new file mode 100644
index 0000000..2adce17
--- /dev/null
+++ b/libide/editor/ide-editor-print-operation.c
@@ -0,0 +1,207 @@
+/* ide-editor-print-operation.c
+ *
+ * Copyright (C) 2015 Paolo Borelli <pborelli gnome org>
+ *
+ * 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/>.
+ */
+
+#define G_LOG_DOMAIN "ide-editor-print-operation"
+
+#include <glib/gi18n.h>
+#include <gtksourceview/gtksource.h>
+#include <ide.h>
+
+#include "ide-editor-print-operation.h"
+#include "ide-editor-view.h"
+
+struct _IdeEditorPrintOperation
+{
+  GtkPrintOperation parent_instance;
+
+  IdeSourceView *view;
+  GtkSourcePrintCompositor *compositor;
+};
+
+G_DEFINE_TYPE (IdeEditorPrintOperation, ide_editor_print_operation, GTK_TYPE_PRINT_OPERATION)
+
+enum {
+  PROP_0,
+  PROP_VIEW,
+  LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+ide_editor_print_operation_dispose (GObject *object)
+{
+  IdeEditorPrintOperation *self = IDE_EDITOR_PRINT_OPERATION (object);
+
+  g_clear_object (&self->compositor);
+
+  G_OBJECT_CLASS (ide_editor_print_operation_parent_class)->dispose (object);
+}
+
+static void
+ide_editor_view_print_get_property (GObject    *object,
+                                   guint       prop_id,
+                                   GValue     *value,
+                                   GParamSpec *pspec)
+{
+  IdeEditorPrintOperation *self = IDE_EDITOR_PRINT_OPERATION (object);
+
+  switch (prop_id)
+    {
+    case PROP_VIEW:
+      g_value_set_object (value, self->view);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_editor_view_print_set_property (GObject      *object,
+                                   guint         prop_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
+{
+  IdeEditorPrintOperation *self = IDE_EDITOR_PRINT_OPERATION (object);
+
+  switch (prop_id)
+    {
+    case PROP_VIEW:
+      self->view = g_value_get_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_editor_print_operation_begin_print (GtkPrintOperation *operation,
+                                       GtkPrintContext   *context)
+{
+  IdeEditorPrintOperation *self = IDE_EDITOR_PRINT_OPERATION (operation);
+  GtkSourceBuffer *buffer;
+  guint tab_width;
+  gboolean syntax_hl;
+
+  buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->view)));
+
+  tab_width = gtk_source_view_get_tab_width (GTK_SOURCE_VIEW (self->view));
+  syntax_hl = gtk_source_buffer_get_highlight_syntax (buffer);
+
+  self->compositor = GTK_SOURCE_PRINT_COMPOSITOR (
+    g_object_new (GTK_SOURCE_TYPE_PRINT_COMPOSITOR,
+                  "buffer", buffer,
+                  "tab-width", tab_width,
+                  "highlight-syntax", syntax_hl,
+                  NULL));
+}
+
+static gboolean
+ide_editor_print_operation_paginate (GtkPrintOperation *operation,
+                                    GtkPrintContext   *context)
+{
+  IdeEditorPrintOperation *self = IDE_EDITOR_PRINT_OPERATION (operation);
+  gboolean finished;
+
+  finished = gtk_source_print_compositor_paginate (self->compositor, context);
+
+  if (finished)
+    {
+      gint n_pages;
+
+      n_pages = gtk_source_print_compositor_get_n_pages (self->compositor);
+      gtk_print_operation_set_n_pages (operation, n_pages);
+    }
+
+  return finished;
+}
+
+static void
+ide_editor_print_operation_draw_page (GtkPrintOperation *operation,
+                                     GtkPrintContext   *context,
+                                     gint               page_nr)
+{
+  IdeEditorPrintOperation *self = IDE_EDITOR_PRINT_OPERATION (operation);
+
+  gtk_source_print_compositor_draw_page (self->compositor, context, page_nr);
+}
+
+static void
+ide_editor_print_operation_end_print (GtkPrintOperation *operation,
+                                     GtkPrintContext   *context)
+{
+  IdeEditorPrintOperation *self = IDE_EDITOR_PRINT_OPERATION (operation);
+
+  g_clear_object (&self->compositor);
+}
+
+static void
+ide_editor_print_operation_class_init (IdeEditorPrintOperationClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkPrintOperationClass *operation_class = GTK_PRINT_OPERATION_CLASS (klass);
+
+  object_class->dispose = ide_editor_print_operation_dispose;
+  object_class->get_property = ide_editor_view_print_get_property;
+  object_class->set_property = ide_editor_view_print_set_property;
+
+  operation_class->begin_print = ide_editor_print_operation_begin_print;
+  operation_class->draw_page = ide_editor_print_operation_draw_page;
+  operation_class->end_print = ide_editor_print_operation_end_print;
+
+  properties [PROP_VIEW] =
+    g_param_spec_object ("view",
+                         "View",
+                         "The source view.",
+                         IDE_TYPE_SOURCE_VIEW,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static gboolean
+paginate_cb (GtkPrintOperation *operation,
+             GtkPrintContext   *context,
+             gpointer           user_data)
+{
+  return ide_editor_print_operation_paginate (operation, context);
+}
+
+static void
+ide_editor_print_operation_init (IdeEditorPrintOperation *self)
+{
+  /* FIXME: gtk decides to call paginate only if it sees a pending signal
+   * handler, even if we override the default handler.
+   * So for now we connect to the signal instead of overriding the vfunc
+   * See https://bugzilla.gnome.org/show_bug.cgi?id=345345
+   */
+  g_signal_connect (self, "paginate", G_CALLBACK (paginate_cb), NULL);
+}
+
+IdeEditorPrintOperation *
+ide_editor_print_operation_new (IdeSourceView *view)
+{
+  g_assert (IDE_IS_SOURCE_VIEW (view));
+
+  return g_object_new (IDE_TYPE_EDITOR_PRINT_OPERATION,
+                       "view", view,
+                       "allow-async", TRUE,
+                       NULL);
+}
diff --git a/libide/editor/ide-editor-print-operation.h b/libide/editor/ide-editor-print-operation.h
new file mode 100644
index 0000000..19a569c
--- /dev/null
+++ b/libide/editor/ide-editor-print-operation.h
@@ -0,0 +1,34 @@
+/* ide-editor-print-operation.h
+ *
+ * Copyright (C) 2015 Paolo Borelli <pborelli gnome org>
+ *
+ * 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/>.
+ */
+
+#ifndef IDE_EDITOR_PRINT_OPERATION_H
+#define IDE_EDITOR_PRINT_OPERATION_H
+
+#include "ide-editor-view.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_EDITOR_PRINT_OPERATION (ide_editor_print_operation_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeEditorPrintOperation, ide_editor_print_operation, IDE, EDITOR_PRINT_OPERATION, 
GtkPrintOperation)
+
+IdeEditorPrintOperation  *ide_editor_print_operation_new    (IdeSourceView *view);
+
+G_END_DECLS
+
+#endif /* IDE_EDITOR_PRINT_OPERATION_H */
diff --git a/libide/editor/ide-editor-tweak-widget.c b/libide/editor/ide-editor-tweak-widget.c
new file mode 100644
index 0000000..7304c5f
--- /dev/null
+++ b/libide/editor/ide-editor-tweak-widget.c
@@ -0,0 +1,184 @@
+/* ide-editor-tweak-widget.c
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "editor-tweak"
+
+#include <glib/gi18n.h>
+#include <gtksourceview/gtksource.h>
+
+#include "ide-editor-tweak-widget.h"
+#include "ide-gtk.h"
+
+struct _IdeEditorTweakWidget
+{
+  GtkBin          parent_instance;
+
+  GtkSearchEntry *entry;
+  GtkListBox     *list_box;
+};
+
+G_DEFINE_TYPE (IdeEditorTweakWidget, ide_editor_tweak_widget, GTK_TYPE_BIN)
+
+static GQuark langQuark;
+
+GtkWidget *
+ide_editor_tweak_widget_new (void)
+{
+  return g_object_new (IDE_TYPE_EDITOR_TWEAK_WIDGET, NULL);
+}
+
+static gboolean
+ide_editor_tweak_widget_filter_func (GtkListBoxRow *row,
+                                    gpointer       user_data)
+{
+  GtkSourceLanguage *language;
+  GtkWidget *child;
+  const gchar *needle = user_data;
+  const gchar *lang_id;
+  const gchar *lang_name;
+  g_autofree gchar *lang_name_cf = NULL;
+
+  g_return_val_if_fail (GTK_IS_LIST_BOX_ROW (row), FALSE);
+  g_return_val_if_fail (needle, FALSE);
+
+  child = gtk_bin_get_child (GTK_BIN (row));
+  language = g_object_get_qdata (G_OBJECT (child), langQuark);
+  lang_id = gtk_source_language_get_id (language);
+  lang_name = gtk_source_language_get_name (language);
+  lang_name_cf = g_utf8_casefold (lang_name, -1);
+
+  if (strstr (lang_id, needle) || strstr (lang_name, needle) || strstr (lang_name_cf, needle))
+    return TRUE;
+
+  return FALSE;
+}
+
+static void
+ide_editor_tweak_widget_entry_changed (IdeEditorTweakWidget *self,
+                                      GtkEntry            *entry)
+{
+  const gchar *text;
+  gchar *text_cf;
+
+  g_return_if_fail (IDE_IS_EDITOR_TWEAK_WIDGET (self));
+  g_return_if_fail (GTK_IS_ENTRY (entry));
+
+  text = gtk_entry_get_text (entry);
+
+  if (ide_str_empty0 (text))
+    gtk_list_box_set_filter_func (self->list_box, NULL, NULL, NULL);
+  else
+    {
+      text_cf = g_utf8_casefold (text, -1);
+      gtk_list_box_set_filter_func (self->list_box, ide_editor_tweak_widget_filter_func,
+                                    text_cf, g_free);
+    }
+}
+
+static void
+ide_editor_tweak_widget_row_activated (IdeEditorTweakWidget *self,
+                                      GtkListBoxRow       *row,
+                                      GtkListBox          *list_box)
+{
+  GtkSourceLanguage *lang;
+  const gchar *lang_id;
+  GtkWidget *child;
+  GVariant *param;
+
+  g_return_if_fail (IDE_IS_EDITOR_TWEAK_WIDGET (self));
+  g_return_if_fail (GTK_IS_LIST_BOX_ROW (row));
+  g_return_if_fail (GTK_IS_LIST_BOX (list_box));
+
+  child = gtk_bin_get_child (GTK_BIN (row));
+  lang = g_object_get_qdata (G_OBJECT (child), langQuark);
+
+  if (lang)
+    {
+      lang_id = gtk_source_language_get_id (lang);
+      param = g_variant_new_string (lang_id);
+      ide_widget_action (GTK_WIDGET (self), "view", "language", param);
+    }
+}
+
+static void
+ide_editor_tweak_widget_constructed (GObject *object)
+{
+  IdeEditorTweakWidget *self = (IdeEditorTweakWidget *)object;
+  GtkSourceLanguageManager *manager;
+  GtkSourceLanguage *lang;
+  const gchar * const *lang_ids;
+  guint i;
+
+  g_return_if_fail (IDE_IS_EDITOR_TWEAK_WIDGET (self));
+
+  G_OBJECT_CLASS (ide_editor_tweak_widget_parent_class)->constructed (object);
+
+  manager = gtk_source_language_manager_get_default ();
+  lang_ids = gtk_source_language_manager_get_language_ids (manager);
+
+  for (i = 0; lang_ids [i]; i++)
+    {
+      GtkWidget *row;
+
+      lang = gtk_source_language_manager_get_language (manager, lang_ids [i]);
+      row = g_object_new (GTK_TYPE_LABEL,
+                          "label", gtk_source_language_get_name (lang),
+                          "visible", TRUE,
+                          "xalign", 0.0f,
+                          "margin-end", 6,
+                          "margin-start", 6,
+                          "margin-top", 3,
+                          "margin-bottom", 3,
+                          NULL);
+      g_object_set_qdata (G_OBJECT (row), langQuark, lang);
+      gtk_list_box_insert (self->list_box, row, -1);
+    }
+
+  g_signal_connect_object (self->entry,
+                           "changed",
+                           G_CALLBACK (ide_editor_tweak_widget_entry_changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->list_box,
+                           "row-activated",
+                           G_CALLBACK (ide_editor_tweak_widget_row_activated),
+                           self,
+                           G_CONNECT_SWAPPED);
+}
+
+static void
+ide_editor_tweak_widget_class_init (IdeEditorTweakWidgetClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->constructed = ide_editor_tweak_widget_constructed;
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/ui/ide-editor-tweak-widget.ui");
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorTweakWidget, entry);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorTweakWidget, list_box);
+
+  langQuark = g_quark_from_static_string ("GtkSourceLanguage");
+}
+
+static void
+ide_editor_tweak_widget_init (IdeEditorTweakWidget *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
diff --git a/libide/editor/ide-editor-tweak-widget.h b/libide/editor/ide-editor-tweak-widget.h
new file mode 100644
index 0000000..4cbc5b1
--- /dev/null
+++ b/libide/editor/ide-editor-tweak-widget.h
@@ -0,0 +1,33 @@
+/* ide-editor-tweak-widget.h
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_EDITOR_TWEAK_WIDGET_H
+#define IDE_EDITOR_TWEAK_WIDGET_H
+
+#include <gtk/gtk.h>
+#include <ide.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_EDITOR_TWEAK_WIDGET (ide_editor_tweak_widget_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeEditorTweakWidget, ide_editor_tweak_widget, IDE, EDITOR_TWEAK_WIDGET, GtkBin)
+
+G_END_DECLS
+
+#endif /* IDE_EDITOR_TWEAK_WIDGET_H */
diff --git a/libide/editor/ide-editor-view-actions.c b/libide/editor/ide-editor-view-actions.c
new file mode 100644
index 0000000..8604215
--- /dev/null
+++ b/libide/editor/ide-editor-view-actions.c
@@ -0,0 +1,824 @@
+/* ide-editor-view-actions.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-editor-view"
+
+#include <glib/gi18n.h>
+#include <string.h>
+
+#include "ide-editor-frame-private.h"
+#include "ide-editor-print-operation.h"
+#include "ide-editor-view-actions.h"
+#include "ide-editor-view-private.h"
+#include "ide-gtk.h"
+
+static void
+ide_editor_view_actions_source_view_notify (IdeSourceView *source_view,
+                                            GParamSpec    *pspec,
+                                            GActionMap    *actions)
+{
+  g_autoptr(GVariant) param = NULL;
+  GtkSourceView *gsv;
+  GAction *action = NULL;
+
+  g_assert (IDE_IS_SOURCE_VIEW (source_view));
+  g_assert (pspec != NULL);
+  g_assert (G_IS_ACTION_MAP (actions));
+
+  gsv = GTK_SOURCE_VIEW (source_view);
+
+  if (g_str_equal (pspec->name, "show-line-numbers"))
+    {
+      gboolean show_line_numbers;
+
+      action = g_action_map_lookup_action (actions, "show-line-numbers");
+      show_line_numbers = gtk_source_view_get_show_line_numbers (gsv);
+      param = g_variant_new_boolean (show_line_numbers);
+    }
+  else if (g_str_equal (pspec->name, "show-right-margin"))
+    {
+      gboolean show_right_margin;
+
+      action = g_action_map_lookup_action (actions, "show-right-margin");
+      show_right_margin = gtk_source_view_get_show_right_margin (gsv);
+      param = g_variant_new_boolean (show_right_margin);
+    }
+  else if (g_str_equal (pspec->name, "highlight-current-line"))
+    {
+      gboolean highlight_current_line;
+
+      action = g_action_map_lookup_action (actions, "highlight-current-line");
+      g_object_get (gsv, "highlight-current-line", &highlight_current_line, NULL);
+      param = g_variant_new_boolean (highlight_current_line);
+    }
+  else if (g_str_equal (pspec->name, "auto-indent"))
+    {
+      gboolean auto_indent;
+
+      action = g_action_map_lookup_action (actions, "auto-indent");
+      g_object_get (source_view, "auto-indent", &auto_indent, NULL);
+      param = g_variant_new_boolean (auto_indent);
+    }
+  else if (g_str_equal (pspec->name, "tab-width"))
+    {
+      guint tab_width;
+
+      action = g_action_map_lookup_action (actions, "tab-width");
+      g_object_get (source_view, "tab-width", &tab_width, NULL);
+      param = g_variant_new_int32 (tab_width);
+    }
+  else if (g_str_equal (pspec->name, "insert-spaces-instead-of-tabs"))
+    {
+      gboolean use_spaces;
+
+      action = g_action_map_lookup_action (actions, "use-spaces");
+      g_object_get (source_view, "insert-spaces-instead-of-tabs", &use_spaces, NULL);
+      param = g_variant_new_boolean (use_spaces);
+    }
+  else if (g_str_equal (pspec->name, "smart-backspace"))
+    {
+      gboolean smart_backspace;
+
+      action = g_action_map_lookup_action (actions, "smart-backspace");
+      g_object_get (source_view, "smart-backspace", &smart_backspace, NULL);
+      param = g_variant_new_boolean (smart_backspace);
+    }
+
+  if (action && param)
+    {
+      g_simple_action_set_state (G_SIMPLE_ACTION (action), param);
+      param = NULL;
+    }
+}
+
+static void
+ide_editor_view_actions_language (GSimpleAction *action,
+                                  GVariant      *variant,
+                                  gpointer       user_data)
+{
+  IdeEditorView *self = user_data;
+  GtkSourceLanguageManager *manager;
+  GtkSourceLanguage *language;
+  GtkSourceBuffer *buffer;
+  const gchar *name;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+
+  manager = gtk_source_language_manager_get_default ();
+  name = g_variant_get_string (variant, NULL);
+  buffer = GTK_SOURCE_BUFFER (self->document);
+
+  if (name != NULL)
+    {
+      language = gtk_source_language_manager_get_language (manager, name);
+      gtk_source_buffer_set_language (buffer, language);
+      ide_editor_view_actions_update (self);
+    }
+}
+
+#define STATE_HANDLER_BOOLEAN(name,propname)                       \
+static void                                                        \
+ide_editor_view_actions_##name (GSimpleAction *action,             \
+                                GVariant      *variant,            \
+                                gpointer       user_data)          \
+{                                                                  \
+  IdeEditorView *self = user_data;                                 \
+  gboolean val;                                                    \
+                                                                   \
+  g_assert (IDE_IS_EDITOR_VIEW (self));                            \
+                                                                   \
+  val = g_variant_get_boolean (variant);                           \
+  g_object_set (self->frame1->source_view, propname, val, NULL);   \
+  if (self->frame2)                                                \
+    g_object_set (self->frame2->source_view, propname, val, NULL); \
+}
+
+#define STATE_HANDLER_INT(name,propname)                           \
+static void                                                        \
+ide_editor_view_actions_##name (GSimpleAction *action,             \
+                                GVariant      *variant,            \
+                                gpointer       user_data)          \
+{                                                                  \
+  IdeEditorView *self = user_data;                                 \
+  gint val;                                                        \
+                                                                   \
+  g_assert (IDE_IS_EDITOR_VIEW (self));                            \
+                                                                   \
+  val = g_variant_get_int32 (variant);                             \
+  g_object_set (self->frame1->source_view, propname, val, NULL);   \
+  if (self->frame2)                                                \
+    g_object_set (self->frame2->source_view, propname, val, NULL); \
+}
+
+STATE_HANDLER_BOOLEAN (auto_indent, "auto-indent")
+STATE_HANDLER_BOOLEAN (show_line_numbers, "show-line-numbers")
+STATE_HANDLER_BOOLEAN (show_right_margin, "show-right-margin")
+STATE_HANDLER_BOOLEAN (highlight_current_line, "highlight-current-line")
+STATE_HANDLER_BOOLEAN (use_spaces, "insert-spaces-instead-of-tabs")
+STATE_HANDLER_BOOLEAN (smart_backspace, "smart-backspace")
+STATE_HANDLER_INT (tab_width, "tab-width")
+
+static void
+save_file_cb (GObject      *object,
+              GAsyncResult *result,
+              gpointer      user_data)
+{
+  IdeBufferManager *buffer_manager = (IdeBufferManager *)object;
+  g_autoptr(IdeEditorView) self = user_data;
+  GError *error = NULL;
+
+  if (!ide_buffer_manager_save_file_finish (buffer_manager, result, &error))
+    {
+      /* info bar */
+      g_warning ("%s", error->message);
+      g_clear_error (&error);
+    }
+
+  ide_widget_hide_with_fade (GTK_WIDGET (self->progress_bar));
+}
+
+static void
+ide_editor_view_actions__save_temp_cb (GObject      *object,
+                                       GAsyncResult *result,
+                                       gpointer      user_data)
+{
+  IdeEditorView *self = user_data;
+  IdeBufferManager *buffer_manager = (IdeBufferManager *)object;
+  GError *error = NULL;
+
+  if (!ide_buffer_manager_save_file_finish (buffer_manager, result, &error))
+    {
+      g_warning ("%s", error->message);
+      g_clear_error (&error);
+    }
+
+  g_object_unref (self);
+}
+
+static void
+save_temp_response (GtkWidget *widget,
+                    gint       response,
+                    gpointer   user_data)
+{
+  g_autoptr(IdeEditorView) self = user_data;
+  g_autoptr(GFile) target = NULL;
+  g_autoptr(IdeProgress) progress = NULL;
+  GtkFileChooser *chooser = (GtkFileChooser *)widget;
+
+  g_assert (GTK_IS_FILE_CHOOSER (chooser));
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+
+  switch (response)
+    {
+    case GTK_RESPONSE_OK:
+      target = gtk_file_chooser_get_file (chooser);
+      break;
+
+    case GTK_RESPONSE_CANCEL:
+    default:
+      break;
+    }
+
+  if (target != NULL)
+    {
+      IdeBufferManager *buffer_manager;
+      IdeContext *context;
+      IdeProject *project;
+      IdeBuffer *buffer = IDE_BUFFER (self->document);
+      g_autoptr(IdeFile) file = NULL;
+
+      context = ide_buffer_get_context (buffer);
+      project = ide_context_get_project (context);
+      buffer_manager = ide_context_get_buffer_manager (context);
+      file = ide_project_get_project_file (project, target);
+
+      ide_buffer_set_file (buffer, file);
+
+      ide_buffer_manager_save_file_async (buffer_manager,
+                                          buffer,
+                                          file,
+                                          &progress,
+                                          NULL,
+                                          ide_editor_view_actions__save_temp_cb,
+                                          g_object_ref (self));
+    }
+
+  gtk_widget_destroy (widget);
+}
+
+static void
+ide_editor_view_actions_save (GSimpleAction *action,
+                              GVariant      *param,
+                              gpointer       user_data)
+{
+  IdeEditorView *self = user_data;
+  IdeContext *context;
+  IdeBufferManager *buffer_manager;
+  IdeFile *file;
+  IdeProgress *progress = NULL;
+  IdeVcs *vcs;
+  GFile *workdir;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+
+  file = ide_buffer_get_file (IDE_BUFFER (self->document));
+  context = ide_buffer_get_context (IDE_BUFFER (self->document));
+  buffer_manager = ide_context_get_buffer_manager (context);
+  vcs = ide_context_get_vcs (context);
+  workdir = ide_vcs_get_working_directory (vcs);
+
+  if (ide_file_get_is_temporary (file))
+    {
+      GtkDialog *dialog;
+      GtkWidget *toplevel;
+      GtkWidget *suggested;
+
+      toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
+      dialog = g_object_new (GTK_TYPE_FILE_CHOOSER_DIALOG,
+                             "action", GTK_FILE_CHOOSER_ACTION_SAVE,
+                             "do-overwrite-confirmation", TRUE,
+                             "local-only", FALSE,
+                             "modal", TRUE,
+                             "select-multiple", FALSE,
+                             "show-hidden", FALSE,
+                             "transient-for", toplevel,
+                             "title", _("Save Document"),
+                             NULL);
+
+      gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (dialog), workdir, NULL);
+
+      gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+                              _("Cancel"), GTK_RESPONSE_CANCEL,
+                              _("Save"), GTK_RESPONSE_OK,
+                              NULL);
+      gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+
+      suggested = gtk_dialog_get_widget_for_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+      gtk_style_context_add_class (gtk_widget_get_style_context (suggested),
+                                   GTK_STYLE_CLASS_SUGGESTED_ACTION);
+
+      g_signal_connect (dialog, "response", G_CALLBACK (save_temp_response), g_object_ref (self));
+
+      gtk_window_present (GTK_WINDOW (dialog));
+
+      return;
+    }
+
+  ide_buffer_manager_save_file_async (buffer_manager,
+                                      IDE_BUFFER (self->document),
+                                      file,
+                                      &progress,
+                                      NULL,
+                                      save_file_cb,
+                                      g_object_ref (self));
+  g_object_bind_property (progress, "fraction", self->progress_bar, "fraction",
+                          G_BINDING_SYNC_CREATE);
+  gtk_widget_show (GTK_WIDGET (self->progress_bar));
+  g_clear_object (&progress);
+}
+
+static void
+ide_editor_view_actions__save_as_cb (GObject      *object,
+                                     GAsyncResult *result,
+                                     gpointer      user_data)
+{
+  IdeEditorView *self = user_data;
+  IdeBufferManager *buffer_manager = (IdeBufferManager *)object;
+  GError *error = NULL;
+
+  if (!ide_buffer_manager_save_file_finish (buffer_manager, result, &error))
+    {
+      g_warning ("%s", error->message);
+      g_clear_error (&error);
+    }
+
+  g_object_unref (self);
+}
+
+static void
+save_as_response (GtkWidget *widget,
+                  gint       response,
+                  gpointer   user_data)
+{
+  g_autoptr(IdeEditorView) self = user_data;
+  g_autoptr(GFile) target = NULL;
+  g_autoptr(IdeProgress) progress = NULL;
+  GtkFileChooser *chooser = (GtkFileChooser *)widget;
+
+  g_assert (GTK_IS_FILE_CHOOSER (chooser));
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+
+  switch (response)
+    {
+    case GTK_RESPONSE_OK:
+      target = gtk_file_chooser_get_file (chooser);
+      break;
+
+    case GTK_RESPONSE_CANCEL:
+    default:
+      break;
+    }
+
+  if (target != NULL)
+    {
+      IdeBufferManager *buffer_manager;
+      IdeContext *context;
+      IdeProject *project;
+      IdeBuffer *buffer = IDE_BUFFER (self->document);
+      g_autoptr(IdeFile) file = NULL;
+
+      context = ide_buffer_get_context (buffer);
+      project = ide_context_get_project (context);
+      buffer_manager = ide_context_get_buffer_manager (context);
+      file = ide_project_get_project_file (project, target);
+
+      ide_buffer_manager_save_file_async (buffer_manager,
+                                          buffer,
+                                          file,
+                                          &progress,
+                                          NULL,
+                                          ide_editor_view_actions__save_as_cb,
+                                          g_object_ref (self));
+    }
+
+  gtk_widget_destroy (widget);
+}
+
+static void
+ide_editor_view_actions_save_as (GSimpleAction *action,
+                                 GVariant      *param,
+                                 gpointer       user_data)
+{
+  IdeEditorView *self = user_data;
+  IdeBuffer *buffer;
+  GtkWidget *suggested;
+  GtkWidget *toplevel;
+  GtkWidget *dialog;
+  IdeFile *file;
+  GFile *gfile;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+
+  toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
+  dialog = g_object_new (GTK_TYPE_FILE_CHOOSER_DIALOG,
+                         "action", GTK_FILE_CHOOSER_ACTION_SAVE,
+                         "do-overwrite-confirmation", TRUE,
+                         "local-only", FALSE,
+                         "modal", TRUE,
+                         "select-multiple", FALSE,
+                         "show-hidden", FALSE,
+                         "transient-for", toplevel,
+                         "title", _("Save Document As"),
+                         NULL);
+
+  buffer = IDE_BUFFER (self->document);
+  file = ide_buffer_get_file (buffer);
+  gfile = ide_file_get_file (file);
+
+  if (gfile != NULL)
+    gtk_file_chooser_set_file (GTK_FILE_CHOOSER (dialog), gfile, NULL);
+
+  gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+                          _("Cancel"), GTK_RESPONSE_CANCEL,
+                          _("Save"), GTK_RESPONSE_OK,
+                          NULL);
+  gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+
+  suggested = gtk_dialog_get_widget_for_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+  gtk_style_context_add_class (gtk_widget_get_style_context (suggested),
+                               GTK_STYLE_CLASS_SUGGESTED_ACTION);
+
+  g_signal_connect (dialog, "response", G_CALLBACK (save_as_response), g_object_ref (self));
+
+  gtk_window_present (GTK_WINDOW (dialog));
+}
+
+static gboolean
+set_split_view (gpointer data)
+{
+  g_autoptr(IdeEditorView) self = data;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+
+  ide_layout_view_set_split_view (IDE_LAYOUT_VIEW (self), (self->frame2 == NULL));
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+ide_editor_view_actions_toggle_split (GSimpleAction *action,
+                                      GVariant      *param,
+                                      gpointer       user_data)
+{
+  IdeEditorView *self = user_data;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+
+  g_timeout_add (0, set_split_view, g_object_ref (self));
+}
+
+static void
+ide_editor_view_actions_close (GSimpleAction *action,
+                               GVariant      *param,
+                               gpointer       user_data)
+{
+  IdeEditorView *self = user_data;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+
+  /* just close our current frame if we have split view */
+  if (self->frame2 != NULL)
+    {
+      /* todo: swap frame1/frame2 if frame2 was last focused. */
+      g_timeout_add (0, set_split_view, g_object_ref (self));
+    }
+  else
+    {
+      ide_widget_action (GTK_WIDGET (self), "view-stack", "close", NULL);
+    }
+}
+
+static void
+find_other_file_cb (GObject      *object,
+                    GAsyncResult *result,
+                    gpointer      user_data)
+{
+  g_autoptr(IdeEditorView) self = user_data;
+  g_autoptr(IdeFile) ret = NULL;
+  IdeFile *file = (IdeFile *)object;
+
+  ret = ide_file_find_other_finish (file, result, NULL);
+
+  if (ret != NULL)
+    {
+      IdeWorkbench *workbench;
+      GFile *gfile;
+
+      gfile = ide_file_get_file (ret);
+      workbench = ide_widget_get_workbench (GTK_WIDGET (self));
+      ide_workbench_open_files_async (workbench, &gfile, 1, NULL, NULL, NULL);
+    }
+}
+
+static void
+ide_editor_view_actions_find_other_file (GSimpleAction *action,
+                                         GVariant      *param,
+                                         gpointer       user_data)
+{
+  IdeEditorView *self = user_data;
+  IdeFile *file;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+
+  file = ide_buffer_get_file (IDE_BUFFER (self->document));
+  ide_file_find_other_async (file, NULL, find_other_file_cb, g_object_ref (self));
+}
+
+static void
+ide_editor_view_actions_reload_buffer_cb (GObject      *object,
+                                          GAsyncResult *result,
+                                          gpointer      user_data)
+{
+  IdeBufferManager *buffer_manager = (IdeBufferManager *)object;
+  g_autoptr(IdeEditorView) self = user_data;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(IdeBuffer) buffer = NULL;
+
+  g_assert (IDE_IS_BUFFER_MANAGER (buffer_manager));
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+
+  gtk_revealer_set_reveal_child (self->modified_revealer, FALSE);
+
+  if (!(buffer = ide_buffer_manager_load_file_finish (buffer_manager, result, &error)))
+    {
+      g_warning ("%s", error->message);
+    }
+  else
+    {
+      g_signal_emit_by_name (self->frame1->source_view, "movement",
+                             IDE_SOURCE_VIEW_MOVEMENT_FIRST_LINE, FALSE, TRUE,
+                             FALSE);
+      if (self->frame2 != NULL)
+        g_signal_emit_by_name (self->frame2->source_view, "movement",
+                               IDE_SOURCE_VIEW_MOVEMENT_FIRST_LINE, FALSE, TRUE,
+                               FALSE);
+    }
+
+  ide_widget_hide_with_fade (GTK_WIDGET (self->progress_bar));
+}
+
+static void
+ide_editor_view_actions_reload_buffer (GSimpleAction *action,
+                                       GVariant      *param,
+                                       gpointer       user_data)
+{
+  IdeEditorView *self = user_data;
+  IdeContext *context;
+  IdeBufferManager *buffer_manager;
+  IdeFile *file;
+  g_autoptr(IdeProgress) progress = NULL;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+
+  context = ide_buffer_get_context (IDE_BUFFER (self->document));
+  file = ide_buffer_get_file (IDE_BUFFER (self->document));
+
+  buffer_manager = ide_context_get_buffer_manager (context);
+
+  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (self->progress_bar), 0.0);
+  gtk_widget_show (GTK_WIDGET (self->progress_bar));
+
+  ide_buffer_manager_load_file_async (buffer_manager,
+                                      file,
+                                      TRUE,
+                                      &progress,
+                                      NULL,
+                                      ide_editor_view_actions_reload_buffer_cb,
+                                      g_object_ref (self));
+
+  g_object_bind_property (progress, "fraction", self->progress_bar, "fraction",
+                          G_BINDING_SYNC_CREATE);
+}
+
+static void
+ide_editor_view_actions_preview (GSimpleAction *action,
+                                 GVariant      *param,
+                                 gpointer       user_data)
+{
+#if 0
+  IdeEditorView *self = user_data;
+  GtkSourceLanguage *language;
+  const gchar *lang_id = NULL;
+  g_autoptr(GbDocument) document = NULL;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+
+  language = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (self->document));
+  if (!language)
+    return;
+
+  lang_id = gtk_source_language_get_id (language);
+  if (!lang_id)
+    return;
+
+  if (g_str_equal (lang_id, "html"))
+    {
+      document = g_object_new (GB_TYPE_HTML_DOCUMENT,
+                               "buffer", self->document,
+                               NULL);
+    }
+  else if (g_str_equal (lang_id, "markdown"))
+    {
+      document = g_object_new (GB_TYPE_HTML_DOCUMENT,
+                               "buffer", self->document,
+                               NULL);
+      gb_html_document_set_transform_func (GB_HTML_DOCUMENT (document),
+                                           gb_html_markdown_transform);
+    }
+
+  if (document)
+    {
+      GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (self));
+
+      while (parent && !IDE_IS_LAYOUT_GRID (parent))
+        parent = gtk_widget_get_parent (parent);
+
+      if (parent == NULL)
+        {
+          while (parent && !IDE_IS_LAYOUT_STACK (parent))
+            parent = gtk_widget_get_parent (parent);
+          g_assert (IDE_IS_LAYOUT_STACK (parent));
+          ide_layout_stack_focus_document (IDE_LAYOUT_STACK (parent), document);
+          return;
+        }
+
+      g_assert (IDE_IS_LAYOUT_GRID (parent));
+      gb_view_grid_focus_document (IDE_LAYOUT_GRID (parent), document);
+    }
+#endif
+}
+
+static void
+ide_editor_view_actions_reveal (GSimpleAction *action,
+                                GVariant      *param,
+                                gpointer       user_data)
+{
+  IdeEditorView *self = user_data;
+  IdeWorkbench *workbench;
+  IdeFile *file;
+  GFile *gfile;
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+
+  file = ide_buffer_get_file (IDE_BUFFER (self->document));
+  gfile = ide_file_get_file (file);
+  workbench = ide_widget_get_workbench (GTK_WIDGET (self));
+
+#if 0
+  gb_workbench_reveal_file (workbench, gfile);
+#endif
+}
+
+static void
+handle_print_result (IdeEditorView           *self,
+                     GtkPrintOperation       *operation,
+                     GtkPrintOperationResult  result)
+{
+  if (result == GTK_PRINT_OPERATION_RESULT_ERROR)
+    {
+      GError *error = NULL;
+
+      gtk_print_operation_get_error (operation, &error);
+
+      /* info bar */
+      g_warning ("%s", error->message);
+      g_clear_error (&error);
+    }
+}
+
+static void
+print_done (GtkPrintOperation       *operation,
+            GtkPrintOperationResult  result,
+            gpointer                 user_data)
+{
+  IdeEditorView *self = user_data;
+
+  handle_print_result (self, operation, result);
+
+  g_object_unref (operation);
+  g_object_unref (self);
+}
+
+static void
+ide_editor_view_actions_print (GSimpleAction *action,
+                               GVariant      *param,
+                               gpointer       user_data)
+{
+  IdeEditorView *self = user_data;
+  GtkWidget *toplevel;
+  g_autoptr(IdeEditorPrintOperation) operation;
+  GtkPrintOperationResult result;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+
+  toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
+
+  operation = ide_editor_print_operation_new (self->frame1->source_view);
+
+  /* keep a ref until "done" is emitted */
+  g_object_ref (operation);
+
+  g_signal_connect_after (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);
+}
+
+static void
+ide_editor_view_actions_goto_line (GSimpleAction *action,
+                                   GVariant      *param,
+                                   gpointer       user_data)
+{
+  IdeEditorView *self = user_data;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+
+  gtk_widget_activate (GTK_WIDGET (self->goto_line_button));
+}
+
+static GActionEntry IdeEditorViewActions[] = {
+  { "auto-indent", NULL, NULL, "false", ide_editor_view_actions_auto_indent },
+  { "close", ide_editor_view_actions_close },
+  { "find-other-file", ide_editor_view_actions_find_other_file },
+  { "goto-line", ide_editor_view_actions_goto_line },
+  { "highlight-current-line", NULL, NULL, "false", ide_editor_view_actions_highlight_current_line },
+  { "language", NULL, "s", "''", ide_editor_view_actions_language },
+  { "preview", ide_editor_view_actions_preview },
+  { "reload-buffer", ide_editor_view_actions_reload_buffer },
+  { "reveal", ide_editor_view_actions_reveal },
+  { "save", ide_editor_view_actions_save },
+  { "save-as", ide_editor_view_actions_save_as },
+  { "print", ide_editor_view_actions_print },
+  { "show-line-numbers", NULL, NULL, "false", ide_editor_view_actions_show_line_numbers },
+  { "show-right-margin", NULL, NULL, "false", ide_editor_view_actions_show_right_margin },
+  { "smart-backspace", NULL, NULL, "false", ide_editor_view_actions_smart_backspace },
+  { "tab-width", NULL, "i", "8", ide_editor_view_actions_tab_width },
+  { "toggle-split", ide_editor_view_actions_toggle_split },
+  { "use-spaces", NULL, "b", "false", ide_editor_view_actions_use_spaces },
+};
+
+void
+ide_editor_view_actions_init (IdeEditorView *self)
+{
+  g_autoptr(GSimpleActionGroup) group = NULL;
+
+  group = g_simple_action_group_new ();
+  g_action_map_add_action_entries (G_ACTION_MAP (group), IdeEditorViewActions,
+                                   G_N_ELEMENTS (IdeEditorViewActions), self);
+  gtk_widget_insert_action_group (GTK_WIDGET (self), "view", G_ACTION_GROUP (group));
+  gtk_widget_insert_action_group (GTK_WIDGET (self->tweak_widget), "view", G_ACTION_GROUP (group));
+
+#define WATCH_PROPERTY(name) \
+  G_STMT_START { \
+    g_signal_connect (self->frame1->source_view, \
+                      "notify::"name, \
+                      G_CALLBACK (ide_editor_view_actions_source_view_notify), \
+                      group); \
+    g_object_notify (G_OBJECT (self->frame1->source_view), name); \
+  } G_STMT_END
+
+  WATCH_PROPERTY ("auto-indent");
+  WATCH_PROPERTY ("highlight-current-line");
+  WATCH_PROPERTY ("insert-spaces-instead-of-tabs");
+  WATCH_PROPERTY ("show-line-numbers");
+  WATCH_PROPERTY ("show-right-margin");
+  WATCH_PROPERTY ("smart-backspace");
+  WATCH_PROPERTY ("tab-width");
+
+#undef WATCH_PROPERTY
+}
+
+void
+ide_editor_view_actions_update (IdeEditorView *self)
+{
+  GtkSourceLanguage *language;
+  const gchar *lang_id = NULL;
+  GActionGroup *group;
+  GAction *action;
+  gboolean enabled;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+
+  group = gtk_widget_get_action_group (GTK_WIDGET (self), "view");
+  if (!G_IS_SIMPLE_ACTION_GROUP (group))
+    return;
+
+  /* update preview sensitivity */
+  language = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (self->document));
+  if (language)
+    lang_id = gtk_source_language_get_id (language);
+  enabled = ((g_strcmp0 (lang_id, "html") == 0) ||
+             (g_strcmp0 (lang_id, "markdown") == 0));
+  action = g_action_map_lookup_action (G_ACTION_MAP (group), "preview");
+  g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled);
+}
diff --git a/libide/editor/ide-editor-view-actions.h b/libide/editor/ide-editor-view-actions.h
new file mode 100644
index 0000000..daab752
--- /dev/null
+++ b/libide/editor/ide-editor-view-actions.h
@@ -0,0 +1,31 @@
+/* ide-editor-view-actions.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_EDITOR_VIEW_ACTIONS_H
+#define IDE_EDITOR_VIEW_ACTIONS_H
+
+#include "ide-editor-view.h"
+
+G_BEGIN_DECLS
+
+void ide_editor_view_actions_init   (IdeEditorView *self);
+void ide_editor_view_actions_update (IdeEditorView *self);
+
+G_END_DECLS
+
+#endif /* IDE_EDITOR_VIEW_ACTIONS_H */
diff --git a/libide/editor/ide-editor-view-addin-private.h b/libide/editor/ide-editor-view-addin-private.h
new file mode 100644
index 0000000..967e985
--- /dev/null
+++ b/libide/editor/ide-editor-view-addin-private.h
@@ -0,0 +1,34 @@
+/* ide-editor-view-addin-private.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_EDITOR_VIEW_ADDIN_PRIVATE_H
+#define IDE_EDITOR_VIEW_ADDIN_PRIVATE_H
+
+#include "ide-editor-view-addin.h"
+
+G_BEGIN_DECLS
+
+void ide_editor_view_addin_language_changed (IdeEditorViewAddin *self,
+                                            const gchar       *language_id);
+void ide_editor_view_addin_load             (IdeEditorViewAddin *self,
+                                            IdeEditorView      *view);
+void ide_editor_view_addin_unload           (IdeEditorViewAddin *self,
+                                            IdeEditorView      *view);
+G_END_DECLS
+
+#endif /* IDE_EDITOR_VIEW_ADDIN_PRIVATE_H */
diff --git a/libide/editor/ide-editor-view-addin.c b/libide/editor/ide-editor-view-addin.c
new file mode 100644
index 0000000..98eb544
--- /dev/null
+++ b/libide/editor/ide-editor-view-addin.c
@@ -0,0 +1,64 @@
+/* ide-editor-view-addin.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ide-editor-view-addin.h"
+
+G_DEFINE_INTERFACE (IdeEditorViewAddin, ide_editor_view_addin, G_TYPE_OBJECT)
+
+static void
+dummy_vfunc (IdeEditorViewAddin *self,
+             IdeEditorView      *view)
+{
+}
+
+static void
+ide_editor_view_addin_default_init (IdeEditorViewAddinInterface *iface)
+{
+  iface->load = dummy_vfunc;
+  iface->unload = dummy_vfunc;
+}
+
+void
+ide_editor_view_addin_load (IdeEditorViewAddin *self,
+                           IdeEditorView      *view)
+{
+  g_return_if_fail (IDE_IS_EDITOR_VIEW_ADDIN (self));
+  g_return_if_fail (IDE_IS_EDITOR_VIEW (view));
+
+  IDE_EDITOR_VIEW_ADDIN_GET_IFACE (self)->load (self, view);
+}
+
+void
+ide_editor_view_addin_unload (IdeEditorViewAddin *self,
+                             IdeEditorView      *view)
+{
+  g_return_if_fail (IDE_IS_EDITOR_VIEW_ADDIN (self));
+  g_return_if_fail (IDE_IS_EDITOR_VIEW (view));
+
+  IDE_EDITOR_VIEW_ADDIN_GET_IFACE (self)->unload (self, view);
+}
+
+void
+ide_editor_view_addin_language_changed (IdeEditorViewAddin *self,
+                                       const gchar       *language_id)
+{
+  g_return_if_fail (IDE_IS_EDITOR_VIEW_ADDIN (self));
+
+  if (IDE_EDITOR_VIEW_ADDIN_GET_IFACE (self)->language_changed)
+    IDE_EDITOR_VIEW_ADDIN_GET_IFACE (self)->language_changed (self, language_id);
+}
diff --git a/libide/editor/ide-editor-view-addin.h b/libide/editor/ide-editor-view-addin.h
new file mode 100644
index 0000000..8622762
--- /dev/null
+++ b/libide/editor/ide-editor-view-addin.h
@@ -0,0 +1,44 @@
+/* ide-editor-view-addin.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_EDITOR_VIEW_ADDIN_H
+#define IDE_EDITOR_VIEW_ADDIN_H
+
+#include "ide-editor-view.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_EDITOR_VIEW_ADDIN (ide_editor_view_addin_get_type ())
+
+G_DECLARE_INTERFACE (IdeEditorViewAddin, ide_editor_view_addin, IDE, EDITOR_VIEW_ADDIN, GObject)
+
+struct _IdeEditorViewAddinInterface
+{
+  GTypeInterface parent;
+
+  void (*load)             (IdeEditorViewAddin *self,
+                            IdeEditorView      *view);
+  void (*unload)           (IdeEditorViewAddin *self,
+                            IdeEditorView      *view);
+  void (*language_changed) (IdeEditorViewAddin *self,
+                            const gchar       *language_id);
+};
+
+G_END_DECLS
+
+#endif /* IDE_EDITOR_VIEW_ADDIN_H */
diff --git a/libide/editor/ide-editor-view-private.h b/libide/editor/ide-editor-view-private.h
new file mode 100644
index 0000000..d5968fd
--- /dev/null
+++ b/libide/editor/ide-editor-view-private.h
@@ -0,0 +1,60 @@
+/* ide-editor-view-private.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_EDITOR_VIEW_PRIVATE_H
+#define IDE_EDITOR_VIEW_PRIVATE_H
+
+#include <libpeas/peas.h>
+#include <gtk/gtk.h>
+
+#include "egg-simple-popover.h"
+
+#include "ide-buffer.h"
+#include "ide-editor-frame.h"
+#include "ide-editor-tweak-widget.h"
+#include "ide-layout-view.h"
+
+G_BEGIN_DECLS
+
+struct _IdeEditorView
+{
+  IdeLayoutView         parent_instance;
+
+  IdeBuffer            *document;
+  PeasExtensionSet     *extensions;
+  GSettings            *settings;
+  gchar                *title;
+
+  GtkLabel             *cursor_label;
+  IdeEditorFrame       *frame1;
+  IdeEditorFrame       *frame2;
+  IdeEditorFrame       *last_focused_frame;
+  GtkButton            *modified_cancel_button;
+  GtkRevealer          *modified_revealer;
+  GtkPaned             *paned;
+  GtkProgressBar       *progress_bar;
+  GtkMenuButton        *tweak_button;
+  IdeEditorTweakWidget *tweak_widget;
+  GtkMenuButton        *goto_line_button;
+  EggSimplePopover     *goto_line_popover;
+  GtkButton            *warning_button;
+};
+
+G_END_DECLS
+
+#endif /* IDE_EDITOR_VIEW_PRIVATE_H */
diff --git a/libide/editor/ide-editor-view.c b/libide/editor/ide-editor-view.c
new file mode 100644
index 0000000..64ac974
--- /dev/null
+++ b/libide/editor/ide-editor-view.c
@@ -0,0 +1,932 @@
+/* ide-editor-view.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-editor-view"
+
+#include <glib/gi18n.h>
+#include <glib/gprintf.h>
+
+#include "egg-simple-popover.h"
+
+#include "ide-editor-frame-private.h"
+#include "ide-editor-view-actions.h"
+#include "ide-editor-view-addin.h"
+#include "ide-editor-view.h"
+#include "ide-editor-view-addin-private.h"
+#include "ide-editor-view-private.h"
+#include "ide-macros.h"
+
+G_DEFINE_TYPE (IdeEditorView, ide_editor_view, IDE_TYPE_LAYOUT_VIEW)
+
+enum {
+  PROP_0,
+  PROP_DOCUMENT,
+  LAST_PROP
+};
+
+enum {
+  REQUEST_DOCUMENTATION,
+  LAST_SIGNAL
+};
+
+static GParamSpec *properties [LAST_PROP];
+static guint signals [LAST_SIGNAL];
+
+static IdeEditorFrame *
+ide_editor_view_get_last_focused (IdeEditorView *self)
+{
+  g_assert (self->last_focused_frame != NULL);
+
+  return self->last_focused_frame;
+}
+
+static void
+ide_editor_view_navigate_to (IdeLayoutView            *view,
+                            IdeSourceLocation *location)
+{
+  IdeEditorView *self = (IdeEditorView *)view;
+  IdeEditorFrame *frame;
+  GtkTextMark *insert;
+  GtkTextBuffer *buffer;
+  GtkTextIter iter;
+  guint line;
+  guint line_offset;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+  g_assert (location != NULL);
+
+  frame = ide_editor_view_get_last_focused (self);
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (frame->source_view));
+
+  line = ide_source_location_get_line (location);
+  line_offset = ide_source_location_get_line_offset (location);
+
+  gtk_text_buffer_get_iter_at_line (buffer, &iter, line);
+  for (; line_offset; line_offset--)
+    if (gtk_text_iter_ends_line (&iter) || !gtk_text_iter_forward_char (&iter))
+      break;
+
+  gtk_text_buffer_select_range (buffer, &iter, &iter);
+
+  insert = gtk_text_buffer_get_insert (buffer);
+  gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (frame->source_view), insert, 0.0, TRUE, 1.0, 0.5);
+
+  g_signal_emit_by_name (frame->source_view, "save-insert-mark");
+
+  IDE_EXIT;
+}
+
+static gboolean
+language_to_string (GBinding     *binding,
+                    const GValue *from_value,
+                    GValue       *to_value,
+                    gpointer      user_data)
+{
+  GtkSourceLanguage *language;
+
+  language = g_value_get_object (from_value);
+
+  if (language != NULL)
+    g_value_set_string (to_value, gtk_source_language_get_name (language));
+  else
+    g_value_set_string (to_value, _("Plain Text"));
+
+  return TRUE;
+}
+
+static gboolean
+ide_editor_view_get_modified (IdeLayoutView *view)
+{
+  IdeEditorView *self = (IdeEditorView *)view;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+
+  return gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (self->document));
+}
+
+static void
+ide_editor_view__buffer_modified_changed (IdeEditorView  *self,
+                                         GParamSpec    *pspec,
+                                         GtkTextBuffer *buffer)
+{
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+
+  g_object_notify (G_OBJECT (self), "modified");
+}
+
+static void
+force_scroll_to_top (IdeSourceView *source_view)
+{
+  GtkAdjustment *vadj;
+  GtkAdjustment *hadj;
+  gdouble lower;
+
+  /*
+   * FIXME:
+   *
+   * See the comment in ide_editor_view__buffer_changed_on_volume()
+   */
+
+  vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (source_view));
+  hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (source_view));
+
+  lower = gtk_adjustment_get_lower (vadj);
+  gtk_adjustment_set_value (vadj, lower);
+
+  lower = gtk_adjustment_get_lower (hadj);
+  gtk_adjustment_set_value (hadj, lower);
+}
+
+static gboolean
+no_really_scroll_to_the_top (gpointer data)
+{
+  g_autoptr(IdeEditorView) self = data;
+
+  force_scroll_to_top (self->frame1->source_view);
+  if (self->frame2 != NULL)
+    force_scroll_to_top (self->frame2->source_view);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+ide_editor_view__buffer_changed_on_volume (IdeEditorView *self,
+                                          GParamSpec   *pspec,
+                                          IdeBuffer    *buffer)
+{
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+  g_assert (IDE_IS_BUFFER (buffer));
+
+  if (ide_buffer_get_changed_on_volume (buffer))
+    gtk_revealer_set_reveal_child (self->modified_revealer, TRUE);
+  else if (gtk_revealer_get_reveal_child (self->modified_revealer))
+    {
+      GtkTextIter iter;
+
+      gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &iter);
+      gtk_text_buffer_select_range (GTK_TEXT_BUFFER (buffer), &iter, &iter);
+
+      /*
+       * FIXME:
+       *
+       * Without this delay, I see a condition with split view where the
+       * non-focused split will just render blank. Well that isn't totally
+       * correct, it renders empty gutters and proper line grid background. But
+       * no textual content. And the adjustment is way out of sync. Even
+       * changing the adjustment manually doesn't help. So whatever, I'll
+       * insert a short delay and we'll pick up after the textview has
+       * stablized.
+       */
+      g_timeout_add (10, no_really_scroll_to_the_top, g_object_ref (self));
+
+      gtk_revealer_set_reveal_child (self->modified_revealer, FALSE);
+    }
+}
+
+static const gchar *
+ide_editor_view_get_special_title (IdeLayoutView *view)
+{
+  return ((IdeEditorView *)view)->title;
+}
+
+static void
+ide_editor_view__buffer_notify_title (IdeEditorView *self,
+                                     GParamSpec   *pspec,
+                                     IdeBuffer    *buffer)
+{
+  const gchar *title;
+  gchar **parts;
+  gboolean needs_prefix;
+  gchar *str;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+  g_assert (IDE_IS_BUFFER (buffer));
+
+  g_free (self->title);
+
+  title = ide_buffer_get_title (buffer);
+
+  if (title == NULL)
+    {
+      /* translators: this shouldn't ever happen */
+      self->title = g_strdup ("untitled");
+      return;
+    }
+
+  if ((needs_prefix = (title [0] == G_DIR_SEPARATOR)))
+    title++;
+
+  parts = g_strsplit (title, G_DIR_SEPARATOR_S, 0);
+  str = g_strjoinv (" "G_DIR_SEPARATOR_S" ", parts);
+
+  if (needs_prefix)
+    {
+      self->title = g_strdup_printf (G_DIR_SEPARATOR_S" %s", str);
+      g_free (str);
+    }
+  else
+    {
+      self->title = str;
+    }
+
+  g_strfreev (parts);
+
+  g_object_notify (G_OBJECT (self), "title");
+}
+
+static void
+notify_language_foreach (PeasExtensionSet *set,
+                         PeasPluginInfo   *plugin_info,
+                         PeasExtension    *exten,
+                         gpointer          user_data)
+{
+  const gchar *language_id = user_data;
+
+  ide_editor_view_addin_language_changed (IDE_EDITOR_VIEW_ADDIN (exten), language_id);
+}
+
+static void
+ide_editor_view__buffer_notify_language (IdeEditorView     *self,
+                                        GParamSpec       *pspec,
+                                        IdeBuffer *document)
+{
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+  g_assert (IDE_IS_BUFFER (document));
+
+  if (self->extensions != NULL)
+    {
+      GtkSourceLanguage *language;
+      const gchar *language_id;
+
+      language = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (document));
+      language_id = language ? gtk_source_language_get_id (language) : NULL;
+
+      peas_extension_set_foreach (self->extensions,
+                                  notify_language_foreach,
+                                  (gchar *)language_id);
+    }
+}
+
+static void
+ide_editor_view__buffer_cursor_moved (IdeEditorView      *self,
+                                     const GtkTextIter *iter,
+                                     GtkTextBuffer     *buffer)
+{
+  GtkTextIter bounds;
+  GtkTextMark *mark;
+  gchar *str;
+  guint line;
+  gint column;
+  gint column2;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+  g_assert (iter != NULL);
+  g_assert (IDE_IS_BUFFER (buffer));
+
+  ide_source_view_get_visual_position (self->frame1->source_view, &line, (guint *)&column);
+
+  mark = gtk_text_buffer_get_selection_bound (buffer);
+  gtk_text_buffer_get_iter_at_mark (buffer, &bounds, mark);
+
+  if (!gtk_widget_has_focus (GTK_WIDGET (self->frame1->source_view)) ||
+      gtk_text_iter_equal (&bounds, iter) ||
+      (gtk_text_iter_get_line (iter) != gtk_text_iter_get_line (&bounds)))
+    {
+      str = g_strdup_printf ("%d:%d", line + 1, column + 1);
+      gtk_label_set_text (self->cursor_label, str);
+      g_free (str);
+      return;
+    }
+
+  /* We have a selection that is on the same line.
+   * Lets give some detail as to how long the selection is.
+   */
+  column2 = gtk_source_view_get_visual_column (GTK_SOURCE_VIEW (self->frame1->source_view),
+                                               &bounds);
+  str = g_strdup_printf ("%d:%d (%d)", line + 1, column + 1, ABS (column2 - column));
+  gtk_label_set_text (self->cursor_label, str);
+  g_free (str);
+}
+
+static void
+ide_editor_view_set_document (IdeEditorView *self,
+                              IdeBuffer     *document)
+{
+  g_return_if_fail (IDE_IS_EDITOR_VIEW (self));
+  g_return_if_fail (IDE_IS_BUFFER (document));
+
+  if (g_set_object (&self->document, document))
+    {
+      if (self->frame1)
+        ide_editor_frame_set_document (self->frame1, document);
+
+      if (self->frame2)
+        ide_editor_frame_set_document (self->frame2, document);
+
+      g_settings_bind (self->settings, "style-scheme-name",
+                       document, "style-scheme-name",
+                       G_SETTINGS_BIND_GET);
+      g_settings_bind (self->settings, "highlight-matching-brackets",
+                       document, "highlight-matching-brackets",
+                       G_SETTINGS_BIND_GET);
+
+      g_signal_connect_object (document,
+                               "cursor-moved",
+                               G_CALLBACK (ide_editor_view__buffer_cursor_moved),
+                               self,
+                               G_CONNECT_SWAPPED);
+
+      g_object_bind_property_full (document, "language", self->tweak_button,
+                                   "label", G_BINDING_SYNC_CREATE,
+                                   language_to_string, NULL, NULL, NULL);
+
+      g_signal_connect_object (document,
+                               "modified-changed",
+                               G_CALLBACK (ide_editor_view__buffer_modified_changed),
+                               self,
+                               G_CONNECT_SWAPPED);
+
+      g_signal_connect_object (document,
+                               "notify::title",
+                               G_CALLBACK (ide_editor_view__buffer_notify_title),
+                               self,
+                               G_CONNECT_SWAPPED);
+
+      g_signal_connect_object (document,
+                               "notify::language",
+                               G_CALLBACK (ide_editor_view__buffer_notify_language),
+                               self,
+                               G_CONNECT_SWAPPED);
+
+      g_signal_connect_object (document,
+                               "notify::changed-on-volume",
+                               G_CALLBACK (ide_editor_view__buffer_changed_on_volume),
+                               self,
+                               G_CONNECT_SWAPPED);
+
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DOCUMENT]);
+
+      g_object_bind_property (document, "has-diagnostics",
+                              self->warning_button, "visible",
+                              G_BINDING_SYNC_CREATE);
+
+      ide_editor_view__buffer_notify_language (self, NULL, document);
+      ide_editor_view__buffer_notify_title (self, NULL, IDE_BUFFER (document));
+
+      ide_editor_view_actions_update (self);
+    }
+}
+
+static IdeLayoutView *
+ide_editor_view_create_split (IdeLayoutView *view)
+{
+  IdeEditorView *self = (IdeEditorView *)view;
+  IdeLayoutView *ret;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+
+  ret = g_object_new (IDE_TYPE_EDITOR_VIEW,
+                      "document", self->document,
+                      "visible", TRUE,
+                      NULL);
+
+  return ret;
+}
+
+static void
+ide_editor_view_grab_focus (GtkWidget *widget)
+{
+  IdeEditorView *self = (IdeEditorView *)widget;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+  g_assert (IDE_IS_EDITOR_FRAME (self->last_focused_frame));
+
+  gtk_widget_grab_focus (GTK_WIDGET (self->last_focused_frame->source_view));
+}
+
+static void
+ide_editor_view_request_documentation (IdeEditorView  *self,
+                                      IdeSourceView *source_view)
+{
+  g_autofree gchar *word = NULL;
+  IdeBuffer *buffer;
+  GtkTextMark *mark;
+  GtkTextIter iter;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+  g_assert (IDE_IS_SOURCE_VIEW (source_view));
+
+  buffer = IDE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (source_view)));
+  mark = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (buffer));
+  gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer), &iter, mark);
+
+  word = ide_buffer_get_word_at_iter (buffer, &iter);
+
+  g_signal_emit (self, signals [REQUEST_DOCUMENTATION], 0, word);
+}
+
+static void
+ide_editor_view__focused_frame_weak_notify (gpointer  data,
+                                           GObject  *object)
+{
+  IdeEditorView  *self = data;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+
+  self->last_focused_frame = self->frame1;
+}
+
+static gboolean
+ide_editor_view__focus_in_event (IdeEditorView  *self,
+                                GdkEvent      *event,
+                                IdeSourceView *source_view)
+{
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+  g_assert (IDE_IS_SOURCE_VIEW (source_view));
+
+  if (self->last_focused_frame && self->last_focused_frame->source_view == source_view)
+      return FALSE;
+
+  if (self->frame2 && self->frame2->source_view == source_view)
+    {
+      self->last_focused_frame = self->frame2;
+      g_object_weak_ref (G_OBJECT (self->frame2), ide_editor_view__focused_frame_weak_notify, self);
+    }
+  else
+    {
+      g_object_weak_unref (G_OBJECT (self->frame2), ide_editor_view__focused_frame_weak_notify, self);
+      self->last_focused_frame = self->frame1;
+    }
+
+  return FALSE;
+}
+
+static void
+ide_editor_view_set_split_view (IdeLayoutView   *view,
+                               gboolean  split_view)
+{
+  IdeEditorView *self = (IdeEditorView *)view;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+
+  if (split_view && (self->frame2 != NULL))
+    return;
+
+  if (!split_view && (self->frame2 == NULL))
+    return;
+
+  if (split_view)
+    {
+      self->frame2 = g_object_new (IDE_TYPE_EDITOR_FRAME,
+                                   "show-ruler", TRUE,
+                                   "document", self->document,
+                                   "visible", TRUE,
+                                   NULL);
+      g_signal_connect_object (self->frame2->source_view,
+                               "request-documentation",
+                               G_CALLBACK (ide_editor_view_request_documentation),
+                               self,
+                               G_CONNECT_SWAPPED);
+
+      g_signal_connect_object (self->frame2->source_view,
+                               "focus-in-event",
+                               G_CALLBACK (ide_editor_view__focus_in_event),
+                               self,
+                               G_CONNECT_SWAPPED);
+
+      gtk_container_add_with_properties (GTK_CONTAINER (self->paned), GTK_WIDGET (self->frame2),
+                                         "shrink", FALSE,
+                                         "resize", TRUE,
+                                         NULL);
+      gtk_widget_grab_focus (GTK_WIDGET (self->frame2));
+    }
+  else
+    {
+      GtkWidget *copy = GTK_WIDGET (self->frame2);
+
+      self->frame2 = NULL;
+      gtk_container_remove (GTK_CONTAINER (self->paned), copy);
+      gtk_widget_grab_focus (GTK_WIDGET (self->frame1));
+    }
+}
+
+static void
+ide_editor_view_set_back_forward_list (IdeLayoutView             *view,
+                                      IdeBackForwardList *back_forward_list)
+{
+  IdeEditorView *self = (IdeEditorView *)view;
+
+  g_assert (IDE_IS_LAYOUT_VIEW (view));
+  g_assert (IDE_IS_BACK_FORWARD_LIST (back_forward_list));
+
+  g_object_set (self->frame1, "back-forward-list", back_forward_list, NULL);
+  if (self->frame2)
+    g_object_set (self->frame2, "back-forward-list", back_forward_list, NULL);
+}
+
+static void
+ide_editor_view_hide_reload_bar (IdeEditorView *self,
+                                GtkWidget    *button)
+{
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+
+  gtk_revealer_set_reveal_child (self->modified_revealer, FALSE);
+}
+
+static GtkSizeRequestMode
+ide_editor_view_get_request_mode (GtkWidget *widget)
+{
+  return GTK_SIZE_REQUEST_CONSTANT_SIZE;
+}
+
+static void
+ide_editor_view_get_preferred_height (GtkWidget *widget,
+                                     gint      *min_height,
+                                     gint      *nat_height)
+{
+  /*
+   * FIXME: Workaround GtkStack changes.
+   *
+   * This can probably be removed once upstream changes land.
+   *
+   * This ignores our potential giant size requests since we don't actually
+   * care about keeping our size requests between animated transitions in
+   * the stack.
+   */
+  GTK_WIDGET_CLASS (ide_editor_view_parent_class)->get_preferred_height (widget, min_height, nat_height);
+  *nat_height = *min_height;
+}
+
+static void
+ide_editor_view_goto_line_activate (IdeEditorView    *self,
+                                    const gchar      *text,
+                                    EggSimplePopover *popover)
+{
+  gint64 value;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+  g_assert (EGG_IS_SIMPLE_POPOVER (popover));
+
+  if (!ide_str_empty0 (text))
+    {
+      value = g_ascii_strtoll (text, NULL, 10);
+
+      if ((value > 0) && (value < G_MAXINT))
+        {
+          GtkTextIter iter;
+          GtkTextBuffer *buffer = GTK_TEXT_BUFFER (self->document);
+
+          gtk_widget_grab_focus (GTK_WIDGET (self->frame1->source_view));
+          gtk_text_buffer_get_iter_at_line (buffer, &iter, value - 1);
+          gtk_text_buffer_select_range (buffer, &iter, &iter);
+          ide_source_view_scroll_to_iter (self->frame1->source_view,
+                                          &iter, 0.25, TRUE, 1.0, 0.5, TRUE);
+        }
+    }
+}
+
+static gboolean
+ide_editor_view_goto_line_insert_text (IdeEditorView    *self,
+                                       guint             position,
+                                       const gchar      *chars,
+                                       guint             n_chars,
+                                       EggSimplePopover *popover)
+{
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+  g_assert (EGG_IS_SIMPLE_POPOVER (popover));
+  g_assert (chars != NULL);
+
+  for (; *chars; chars = g_utf8_next_char (chars))
+    {
+      if (!g_unichar_isdigit (g_utf8_get_char (chars)))
+        return GDK_EVENT_STOP;
+    }
+
+  return GDK_EVENT_PROPAGATE;
+}
+
+static void
+ide_editor_view_goto_line_changed (IdeEditorView    *self,
+                                   EggSimplePopover *popover)
+{
+  gchar *message;
+  const gchar *text;
+  GtkTextIter begin;
+  GtkTextIter end;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+  g_assert (EGG_IS_SIMPLE_POPOVER (popover));
+
+  text = egg_simple_popover_get_text (popover);
+
+  gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (self->document), &begin, &end);
+
+  if (!ide_str_empty0 (text))
+    {
+      gint64 value;
+
+      value = g_ascii_strtoll (text, NULL, 10);
+
+      if (value > 0)
+        {
+          if (value <= gtk_text_iter_get_line (&end) + 1)
+            {
+              egg_simple_popover_set_message (popover, NULL);
+              egg_simple_popover_set_ready (popover, TRUE);
+              return;
+            }
+
+        }
+    }
+
+  /* translators: the user selected a number outside the value range for the document. */
+  message = g_strdup_printf (_("Provide a number between 1 and %u"),
+                             gtk_text_iter_get_line (&end) + 1);
+  egg_simple_popover_set_message (popover, message);
+  egg_simple_popover_set_ready (popover, FALSE);
+
+  g_free (message);
+}
+
+static void
+ide_editor_view__extension_added (PeasExtensionSet   *set,
+                                  PeasPluginInfo     *info,
+                                  IdeEditorViewAddin *addin,
+                                  IdeEditorView      *self)
+{
+  g_assert (PEAS_IS_EXTENSION_SET (set));
+  g_assert (info != NULL);
+  g_assert (IDE_IS_EDITOR_VIEW_ADDIN (addin));
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+
+  ide_editor_view_addin_load (addin, self);
+
+  if (self->document != NULL)
+    {
+      GtkSourceLanguage *language;
+
+      language = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (self->document));
+
+      if (language != NULL)
+        {
+          const gchar *language_id;
+
+          language_id = gtk_source_language_get_id (language);
+          ide_editor_view_addin_language_changed (addin, language_id);
+        }
+    }
+}
+
+static void
+ide_editor_view__extension_removed (PeasExtensionSet  *set,
+                                   PeasPluginInfo    *info,
+                                   IdeEditorViewAddin *addin,
+                                   IdeEditorView      *self)
+{
+  g_assert (PEAS_IS_EXTENSION_SET (set));
+  g_assert (info != NULL);
+  g_assert (IDE_IS_EDITOR_VIEW_ADDIN (addin));
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+
+  ide_editor_view_addin_unload (addin, self);
+}
+
+static void
+ide_editor_view_warning_button_clicked (IdeEditorView *self,
+                                       GtkButton    *button)
+{
+  IdeEditorFrame *frame;
+
+  g_assert (IDE_IS_EDITOR_VIEW (self));
+  g_assert (GTK_IS_BUTTON (button));
+
+  frame = ide_editor_view_get_last_focused (self);
+  gtk_widget_grab_focus (GTK_WIDGET (frame));
+  g_signal_emit_by_name (frame->source_view, "move-error", GTK_DIR_DOWN);
+}
+
+static void
+ide_editor_view_constructed (GObject *object)
+{
+  IdeEditorView *self = (IdeEditorView *)object;
+  PeasEngine *engine;
+
+  G_OBJECT_CLASS (ide_editor_view_parent_class)->constructed (object);
+
+  engine = peas_engine_get_default ();
+  self->extensions = peas_extension_set_new (engine, IDE_TYPE_EDITOR_VIEW_ADDIN, NULL);
+  g_signal_connect_object (self->extensions,
+                           "extension-added",
+                           G_CALLBACK (ide_editor_view__extension_added),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (self->extensions,
+                           "extension-added",
+                           G_CALLBACK (ide_editor_view__extension_removed),
+                           self,
+                           G_CONNECT_SWAPPED);
+  peas_extension_set_foreach (self->extensions,
+                              (PeasExtensionSetForeachFunc)ide_editor_view__extension_added,
+                              self);
+}
+
+static void
+ide_editor_view_destroy (GtkWidget *widget)
+{
+  IdeEditorView *self = (IdeEditorView *)widget;
+
+  GTK_WIDGET_CLASS (ide_editor_view_parent_class)->destroy (widget);
+
+  g_clear_object (&self->document);
+}
+
+static void
+ide_editor_view_finalize (GObject *object)
+{
+  IdeEditorView *self = (IdeEditorView *)object;
+
+  g_clear_pointer (&self->title, g_free);
+  g_clear_object (&self->extensions);
+  g_clear_object (&self->document);
+  g_clear_object (&self->settings);
+
+  G_OBJECT_CLASS (ide_editor_view_parent_class)->finalize (object);
+}
+
+static void
+ide_editor_view_get_property (GObject    *object,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+  IdeEditorView *self = IDE_EDITOR_VIEW (object);
+
+  switch (prop_id)
+    {
+    case PROP_DOCUMENT:
+      g_value_set_object (value, self->document);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_editor_view_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+  IdeEditorView *self = IDE_EDITOR_VIEW (object);
+
+  switch (prop_id)
+    {
+    case PROP_DOCUMENT:
+      ide_editor_view_set_document (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_editor_view_class_init (IdeEditorViewClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  IdeLayoutViewClass *view_class = IDE_LAYOUT_VIEW_CLASS (klass);
+
+  object_class->constructed = ide_editor_view_constructed;
+  object_class->finalize = ide_editor_view_finalize;
+  object_class->get_property = ide_editor_view_get_property;
+  object_class->set_property = ide_editor_view_set_property;
+
+  widget_class->destroy = ide_editor_view_destroy;
+  widget_class->grab_focus = ide_editor_view_grab_focus;
+  widget_class->get_request_mode = ide_editor_view_get_request_mode;
+  widget_class->get_preferred_height = ide_editor_view_get_preferred_height;
+
+  view_class->create_split = ide_editor_view_create_split;
+  view_class->get_special_title = ide_editor_view_get_special_title;
+  view_class->get_modified = ide_editor_view_get_modified;
+  view_class->set_split_view = ide_editor_view_set_split_view;
+  view_class->set_back_forward_list = ide_editor_view_set_back_forward_list;
+  view_class->navigate_to = ide_editor_view_navigate_to;
+
+  properties [PROP_DOCUMENT] =
+    g_param_spec_object ("document",
+                         "Document",
+                         "The editor document.",
+                         IDE_TYPE_BUFFER,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+  signals [REQUEST_DOCUMENTATION] =
+    g_signal_new ("request-documentation",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL, NULL,
+                  G_TYPE_NONE,
+                  1,
+                  G_TYPE_STRING);
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/builder/ui/ide-editor-view.ui");
+
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorView, cursor_label);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorView, frame1);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorView, modified_cancel_button);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorView, modified_revealer);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorView, paned);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorView, progress_bar);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorView, tweak_button);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorView, tweak_widget);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorView, goto_line_button);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorView, goto_line_popover);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorView, warning_button);
+
+  g_type_ensure (IDE_TYPE_EDITOR_FRAME);
+  g_type_ensure (IDE_TYPE_EDITOR_TWEAK_WIDGET);
+}
+
+static void
+ide_editor_view_init (IdeEditorView *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  self->settings = g_settings_new ("org.gnome.builder.editor");
+  self->last_focused_frame = self->frame1;
+
+  ide_editor_view_actions_init (self);
+
+  /*
+   * XXX: Refactor all of this.
+   *
+   * In frame1, we don't show the floating bar, so no need to alter the
+   * editor map allocation.
+   */
+  g_object_set (self->frame1->source_map_container,
+                "floating-bar", NULL,
+                NULL);
+
+  g_signal_connect_object (self->modified_cancel_button,
+                           "clicked",
+                           G_CALLBACK (ide_editor_view_hide_reload_bar),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->frame1->source_view,
+                           "request-documentation",
+                           G_CALLBACK (ide_editor_view_request_documentation),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->frame1->source_view,
+                           "focus-in-event",
+                           G_CALLBACK (ide_editor_view__focus_in_event),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->goto_line_popover,
+                           "activate",
+                           G_CALLBACK (ide_editor_view_goto_line_activate),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->goto_line_popover,
+                           "insert-text",
+                           G_CALLBACK (ide_editor_view_goto_line_insert_text),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->goto_line_popover,
+                           "changed",
+                           G_CALLBACK (ide_editor_view_goto_line_changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->warning_button,
+                           "clicked",
+                           G_CALLBACK (ide_editor_view_warning_button_clicked),
+                           self,
+                           G_CONNECT_SWAPPED);
+}
diff --git a/libide/editor/ide-editor-view.h b/libide/editor/ide-editor-view.h
new file mode 100644
index 0000000..e7dba5b
--- /dev/null
+++ b/libide/editor/ide-editor-view.h
@@ -0,0 +1,32 @@
+/* ide-editor-view.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_EDITOR_VIEW_H
+#define IDE_EDITOR_VIEW_H
+
+#include "ide-layout-view.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_EDITOR_VIEW (ide_editor_view_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeEditorView, ide_editor_view, IDE, EDITOR_VIEW, IdeLayoutView)
+
+G_END_DECLS
+
+#endif /* IDE_EDITOR_VIEW_H */
diff --git a/libide/editor/run.sh b/libide/editor/run.sh
new file mode 100755
index 0000000..d4f809e
--- /dev/null
+++ b/libide/editor/run.sh
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+
+base="../../src/editor"
+
+refactor.py \
+       "${base}/gb-editor-frame-actions.c=ide-editor-frame-actions.c" \
+       "${base}/gb-editor-frame-actions.h=ide-editor-frame-actions.h" \
+       "${base}/gb-editor-frame.c=ide-editor-frame.c" \
+       "${base}/gb-editor-frame.h=ide-editor-frame.h" \
+       "${base}/gb-editor-frame-private.h=ide-editor-frame-private.h" \
+       "${base}/gb-editor-map-bin.c=ide-editor-map-bin.c" \
+       "${base}/gb-editor-map-bin.h=ide-editor-map-bin.h" \
+       "${base}/gb-editor-print-operation.c=ide-editor-print-operation.c" \
+       "${base}/gb-editor-print-operation.h=ide-editor-print-operation.h" \
+       "${base}/gb-editor-settings-widget.c=ide-editor-settings-widget.c" \
+       "${base}/gb-editor-settings-widget.h=ide-editor-settings-widget.h" \
+       "${base}/gb-editor-tweak-widget.c=ide-editor-tweak-widget.c" \
+       "${base}/gb-editor-tweak-widget.h=ide-editor-tweak-widget.h" \
+       "${base}/gb-editor-view-actions.c=ide-editor-view-actions.c" \
+       "${base}/gb-editor-view-actions.h=ide-editor-view-actions.h" \
+       "${base}/gb-editor-view-addin.c=ide-editor-view-addin.c" \
+       "${base}/gb-editor-view-addin.h=ide-editor-view-addin.h" \
+       "${base}/gb-editor-view-addin-private.h=ide-editor-view-addin-private.h" \
+       "${base}/gb-editor-view.c=ide-editor-view.c" \
+       "${base}/gb-editor-view.h=ide-editor-view.h" \
+       "${base}/gb-editor-view-private.h=ide-editor-view-private.h"
diff --git a/libide/resources/libide.gresource.xml b/libide/resources/libide.gresource.xml
index fbefeb5..7b1bb65 100644
--- a/libide/resources/libide.gresource.xml
+++ b/libide/resources/libide.gresource.xml
@@ -41,6 +41,9 @@
 
   <gresource prefix="/org/gnome/builder/ui">
     <file alias="ide-editor-perspective.ui">../../data/ui/ide-editor-perspective.ui</file>
+    <file alias="ide-editor-frame.ui">../../data/ui/ide-editor-frame.ui</file>
+    <file alias="ide-editor-tweak-widget.ui">../../data/ui/ide-editor-tweak-widget.ui</file>
+    <file alias="ide-editor-view.ui">../../data/ui/ide-editor-view.ui</file>
     <file alias="ide-greeter-perspective.ui">../../data/ui/ide-greeter-perspective.ui</file>
     <file alias="ide-greeter-project-row.ui">../../data/ui/ide-greeter-project-row.ui</file>
     <file alias="ide-layout.ui">../../data/ui/ide-layout.ui</file>
diff --git a/libide/util/ide-dnd.c b/libide/util/ide-dnd.c
new file mode 100644
index 0000000..4165ada
--- /dev/null
+++ b/libide/util/ide-dnd.c
@@ -0,0 +1,42 @@
+/* ide-dnd.c
+ *
+ * Copyright (C) 2015 Dimitris Zenios <dimitris zenios gmail 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/>.
+ */
+
+#include "ide-dnd.h"
+
+/**
+ * ide_dnd_get_uri_list:
+ * @selection_data: the #GtkSelectionData from drag_data_received
+ *
+ * Create a list of valid uri's from a uri-list drop.
+ *
+ * Returns: (transfer full): a string array which will hold the uris or
+ *   %NULL if there were no valid uris. g_strfreev should be used when
+ *   the string array is no longer used
+ */
+gchar **
+ide_dnd_get_uri_list (GtkSelectionData *selection_data)
+{
+  const gchar *data;
+
+  g_return_val_if_fail (selection_data, NULL);
+  g_return_val_if_fail (gtk_selection_data_get_length (selection_data) > 0, NULL);
+
+  data = (const gchar *)gtk_selection_data_get_data (selection_data);
+
+  return g_uri_list_extract_uris (data);
+}
diff --git a/libide/util/ide-dnd.h b/libide/util/ide-dnd.h
new file mode 100644
index 0000000..affac1c
--- /dev/null
+++ b/libide/util/ide-dnd.h
@@ -0,0 +1,30 @@
+/* ide-dnd.h
+ *
+ * Copyright (C) 2015 Dimitris Zenios <dimitris zenios gmail 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/>.
+ */
+
+#ifndef IDE_DND_H
+#define IDE_DND_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+gchar **ide_dnd_get_uri_list (GtkSelectionData *selection_data);
+
+G_END_DECLS
+
+#endif /* GB_RGBA_H */
diff --git a/libide/util/ide-gtk.c b/libide/util/ide-gtk.c
index 26071be..3618458 100644
--- a/libide/util/ide-gtk.c
+++ b/libide/util/ide-gtk.c
@@ -16,6 +16,8 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include "egg-animation.h"
+
 #include "ide-gtk.h"
 
 gboolean
@@ -124,3 +126,97 @@ ide_widget_set_context_handler (gpointer                widget,
   if ((toplevel = gtk_widget_get_toplevel (widget)))
     ide_widget_hierarchy_changed (widget, NULL, NULL);
 }
+
+static void
+show_callback (gpointer data)
+{
+  g_object_set_data (data, "FADE_ANIMATION", NULL);
+  g_object_unref (data);
+}
+
+static void
+hide_callback (gpointer data)
+{
+  GtkWidget *widget = data;
+
+  g_object_set_data (data, "FADE_ANIMATION", NULL);
+  gtk_widget_hide (widget);
+  gtk_widget_set_opacity (widget, 1.0);
+  g_object_unref (widget);
+}
+
+void
+ide_widget_hide_with_fade (GtkWidget *widget)
+{
+  GdkFrameClock *frame_clock;
+  EggAnimation *anim;
+
+  g_return_if_fail (GTK_IS_WIDGET (widget));
+
+  if (gtk_widget_get_visible (widget))
+    {
+      anim = g_object_get_data (G_OBJECT (widget), "FADE_ANIMATION");
+      if (anim != NULL)
+        egg_animation_stop (anim);
+
+      frame_clock = gtk_widget_get_frame_clock (widget);
+      anim = egg_object_animate_full (widget,
+                                      EGG_ANIMATION_LINEAR,
+                                      1000,
+                                      frame_clock,
+                                      hide_callback,
+                                      g_object_ref (widget),
+                                      "opacity", 0.0,
+                                      NULL);
+      g_object_set_data_full (G_OBJECT (widget), "FADE_ANIMATION",
+                              g_object_ref (anim), g_object_unref);
+    }
+}
+
+void
+ide_widget_show_with_fade (GtkWidget *widget)
+{
+  GdkFrameClock *frame_clock;
+  EggAnimation *anim;
+
+  g_return_if_fail (GTK_IS_WIDGET (widget));
+
+  if (!gtk_widget_get_visible (widget))
+    {
+      anim = g_object_get_data (G_OBJECT (widget), "FADE_ANIMATION");
+      if (anim != NULL)
+        egg_animation_stop (anim);
+
+      frame_clock = gtk_widget_get_frame_clock (widget);
+      gtk_widget_set_opacity (widget, 0.0);
+      gtk_widget_show (widget);
+      anim = egg_object_animate_full (widget,
+                                      EGG_ANIMATION_LINEAR,
+                                      500,
+                                      frame_clock,
+                                      show_callback,
+                                      g_object_ref (widget),
+                                      "opacity", 1.0,
+                                      NULL);
+      g_object_set_data_full (G_OBJECT (widget), "FADE_ANIMATION",
+                              g_object_ref (anim), g_object_unref);
+    }
+}
+
+IdeWorkbench *
+ide_widget_get_workbench (GtkWidget *widget)
+{
+  GtkWidget *ancestor;
+
+  g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
+
+  ancestor = gtk_widget_get_ancestor (widget, IDE_TYPE_WORKBENCH);
+  if (IDE_IS_WORKBENCH (ancestor))
+    return IDE_WORKBENCH (ancestor);
+
+  /*
+   * TODO: Add "IDE_WORKBENCH" gdata for popout windows.
+   */
+
+  return NULL;
+}
diff --git a/libide/util/ide-gtk.h b/libide/util/ide-gtk.h
index d3f687a..4b7b00d 100644
--- a/libide/util/ide-gtk.h
+++ b/libide/util/ide-gtk.h
@@ -22,19 +22,21 @@
 #include <gtk/gtk.h>
 
 #include "ide-context.h"
+#include "ide-workbench.h"
 
 G_BEGIN_DECLS
 
 typedef void (*IdeWidgetContextHandler) (GtkWidget  *widget,
                                          IdeContext *context);
 
-gboolean ide_widget_action              (GtkWidget               *widget,
-                                         const gchar             *group,
-                                         const gchar             *name,
-                                         GVariant                *param);
-void     ide_widget_set_context_handler (gpointer                 widget,
-                                         IdeWidgetContextHandler  handler);
-
+gboolean      ide_widget_action              (GtkWidget               *widget,
+                                              const gchar             *group,
+                                              const gchar             *name,
+                                              GVariant                *param);
+void          ide_widget_set_context_handler (gpointer                 widget,
+                                              IdeWidgetContextHandler  handler);
+void          ide_widget_hide_with_fade      (GtkWidget               *widget);
+IdeWorkbench *ide_widget_get_workbench       (GtkWidget               *widget);
 
 G_END_DECLS
 
diff --git a/plugins/command-bar/gb-command-provider.c b/plugins/command-bar/gb-command-provider.c
index 7f8b2f6..480a7d9 100644
--- a/plugins/command-bar/gb-command-provider.c
+++ b/plugins/command-bar/gb-command-provider.c
@@ -105,7 +105,7 @@ on_workbench_set_focus (GbCommandProvider *provider,
                         IdeWorkbench       *workbench)
 {
   g_return_if_fail (GB_IS_COMMAND_PROVIDER (provider));
-  g_return_if_fail (GB_IS_WORKBENCH (workbench));
+  g_return_if_fail (IDE_IS_WORKBENCH (workbench));
   g_return_if_fail (!widget || GTK_IS_WIDGET (widget));
 
   /* walk the hierarchy to find a tab */
@@ -124,7 +124,7 @@ gb_command_provider_connect (GbCommandProvider *provider,
                              IdeWorkbench       *workbench)
 {
   g_return_if_fail (GB_IS_COMMAND_PROVIDER (provider));
-  g_return_if_fail (GB_IS_WORKBENCH (workbench));
+  g_return_if_fail (IDE_IS_WORKBENCH (workbench));
 
   g_signal_connect_object (workbench,
                            "set-focus",
@@ -138,7 +138,7 @@ gb_command_provider_disconnect (GbCommandProvider *provider,
                                 IdeWorkbench       *workbench)
 {
   g_return_if_fail (GB_IS_COMMAND_PROVIDER (provider));
-  g_return_if_fail (GB_IS_WORKBENCH (workbench));
+  g_return_if_fail (IDE_IS_WORKBENCH (workbench));
 
   g_signal_handlers_disconnect_by_func (workbench,
                                         G_CALLBACK (on_workbench_set_focus),
@@ -162,7 +162,7 @@ gb_command_provider_set_workbench (GbCommandProvider *provider,
   GbCommandProviderPrivate *priv = gb_command_provider_get_instance_private (provider);
 
   g_return_if_fail (GB_IS_COMMAND_PROVIDER (provider));
-  g_return_if_fail (!workbench || GB_IS_WORKBENCH (workbench));
+  g_return_if_fail (!workbench || IDE_IS_WORKBENCH (workbench));
 
   if (priv->workbench != workbench)
     {
@@ -317,7 +317,7 @@ gb_command_provider_class_init (GbCommandProviderClass *klass)
     g_param_spec_object ("active-tab",
                          "Active View",
                          "The last focused IdeLayoutView widget.",
-                         GB_TYPE_VIEW,
+                         IDE_TYPE_LAYOUT_VIEW,
                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
   /**
diff --git a/plugins/command-bar/gb-command-vim-provider.c b/plugins/command-bar/gb-command-vim-provider.c
index a59b7aa..4238a7f 100644
--- a/plugins/command-bar/gb-command-vim-provider.c
+++ b/plugins/command-bar/gb-command-vim-provider.c
@@ -46,7 +46,7 @@ get_source_view (GbCommandProvider *provider)
 
   /* Make sure we have a workbench */
   workbench = gb_command_provider_get_workbench (provider);
-  if (!GB_IS_WORKBENCH (workbench))
+  if (!IDE_IS_WORKBENCH (workbench))
     return NULL;
 
   /* Make sure we have an editor tab last focused */


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