[gnome-builder] plugins/editorui: add editorui plugin parallel to editor



commit dea9fd2b56e54b5d549c73341cd2563ed0dbfb12
Author: Christian Hergert <chergert redhat com>
Date:   Mon Jul 11 22:34:53 2022 -0700

    plugins/editorui: add editorui plugin parallel to editor
    
    This follows the "ui" naming convention we have elsewhere for extending
    things internally into helper plugins. Also, this allows us to
    incrementally port things over as we remove them from ../editor/.

 src/plugins/editorui/editorui-plugin.c             |  52 ++
 src/plugins/editorui/editorui.gresource.xml        |  10 +
 src/plugins/editorui/editorui.plugin               |  11 +
 .../editorui/gbp-editorui-application-addin.c      | 427 +++++++++++
 .../editorui/gbp-editorui-application-addin.h      |  31 +
 src/plugins/editorui/gbp-editorui-position-label.c |  79 ++
 src/plugins/editorui/gbp-editorui-position-label.h |  35 +
 .../editorui/gbp-editorui-position-label.ui        |  13 +
 .../editorui/gbp-editorui-preferences-addin.c      | 461 ++++++++++++
 .../editorui/gbp-editorui-preferences-addin.h      |  31 +
 src/plugins/editorui/gbp-editorui-preview.c        | 173 +++++
 src/plugins/editorui/gbp-editorui-preview.h        |  33 +
 .../editorui/gbp-editorui-workbench-addin.c        | 304 ++++++++
 .../editorui/gbp-editorui-workbench-addin.h        |  31 +
 .../editorui/gbp-editorui-workspace-addin.c        | 826 +++++++++++++++++++++
 .../editorui/gbp-editorui-workspace-addin.h        |  31 +
 src/plugins/editorui/gtk/keybindings.json          |   2 +
 src/plugins/editorui/gtk/menus.ui                  | 157 ++++
 src/plugins/editorui/meson.build                   |  17 +
 src/plugins/editorui/style.css                     |  14 +
 20 files changed, 2738 insertions(+)
---
diff --git a/src/plugins/editorui/editorui-plugin.c b/src/plugins/editorui/editorui-plugin.c
new file mode 100644
index 000000000..b8edfbcd2
--- /dev/null
+++ b/src/plugins/editorui/editorui-plugin.c
@@ -0,0 +1,52 @@
+/* editorui-plugin.c
+ *
+ * Copyright 2018-2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "editorui-plugin"
+
+#include "config.h"
+
+#include <libpeas/peas.h>
+
+#include <libide-gui.h>
+
+#include "gbp-editorui-application-addin.h"
+#include "gbp-editorui-preferences-addin.h"
+#include "gbp-editorui-workbench-addin.h"
+#include "gbp-editorui-workspace-addin.h"
+#include "gbp-editorui-resources.h"
+
+_IDE_EXTERN void
+_gbp_editorui_register_types (PeasObjectModule *module)
+{
+  g_resources_register (gbp_editorui_get_resource ());
+
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_APPLICATION_ADDIN,
+                                              GBP_TYPE_EDITORUI_APPLICATION_ADDIN);
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_PREFERENCES_ADDIN,
+                                              GBP_TYPE_EDITORUI_PREFERENCES_ADDIN);
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_WORKBENCH_ADDIN,
+                                              GBP_TYPE_EDITORUI_WORKBENCH_ADDIN);
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_WORKSPACE_ADDIN,
+                                              GBP_TYPE_EDITORUI_WORKSPACE_ADDIN);
+}
diff --git a/src/plugins/editorui/editorui.gresource.xml b/src/plugins/editorui/editorui.gresource.xml
new file mode 100644
index 000000000..9b7ceea9d
--- /dev/null
+++ b/src/plugins/editorui/editorui.gresource.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/plugins/editorui">
+    <file>editorui.plugin</file>
+    <file>style.css</file>
+    <file preprocess="xml-stripblanks">gtk/menus.ui</file>
+    <file>gtk/keybindings.json</file>
+    <file preprocess="xml-stripblanks">gbp-editorui-position-label.ui</file>
+  </gresource>
+</gresources>
diff --git a/src/plugins/editorui/editorui.plugin b/src/plugins/editorui/editorui.plugin
new file mode 100644
index 000000000..7e5b62421
--- /dev/null
+++ b/src/plugins/editorui/editorui.plugin
@@ -0,0 +1,11 @@
+[Plugin]
+Authors=Christian Hergert <christian hergert me>
+Builtin=true
+Copyright=Copyright © 2014-2022 Christian Hergert
+Embedded=_gbp_editorui_register_types
+Hidden=true
+Module=editorui
+Name=Editor UI
+X-At-Startup=true
+X-Workspace-Kind=primary;editor;
+X-Preferences-Kind=application;project;
diff --git a/src/plugins/editorui/gbp-editorui-application-addin.c 
b/src/plugins/editorui/gbp-editorui-application-addin.c
new file mode 100644
index 000000000..b687baab7
--- /dev/null
+++ b/src/plugins/editorui/gbp-editorui-application-addin.c
@@ -0,0 +1,427 @@
+/* gbp-editorui-application-addin.c
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-editorui-application-addin"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <stdlib.h>
+
+#include <libide-editor.h>
+#include <libide-gui.h>
+
+#include "gbp-editorui-application-addin.h"
+
+struct _GbpEditoruiApplicationAddin
+{
+  GObject parent_instance;
+};
+
+static void
+find_workbench_for_dir_cb (IdeWorkbench *workbench,
+                           gpointer      user_data)
+{
+  g_autoptr(GFile) workdir = NULL;
+  IdeContext *context;
+  struct {
+    GFile        *workdir;
+    IdeWorkbench *workbench;
+  } *lookup = user_data;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_WORKBENCH (workbench));
+  g_assert (lookup != NULL);
+
+  if (lookup->workbench != NULL)
+    return;
+
+  context = ide_workbench_get_context (workbench);
+  workdir = ide_context_ref_workdir (context);
+
+  if (g_file_has_prefix (lookup->workdir, workdir) ||
+      g_file_equal (lookup->workdir, workdir))
+    lookup->workbench = workbench;
+}
+
+static IdeWorkbench *
+find_workbench_for_dir (IdeApplication *app,
+                        GFile          *workdir)
+{
+  struct {
+    GFile        *workdir;
+    IdeWorkbench *workbench;
+  } lookup = { workdir, NULL };
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_APPLICATION (app));
+  g_assert (G_IS_FILE (workdir));
+
+  ide_application_foreach_workbench (app,
+                                     (GFunc)find_workbench_for_dir_cb,
+                                     &lookup);
+
+  return lookup.workbench ? g_object_ref (lookup.workbench) : NULL;
+}
+
+static GFile *
+get_common_ancestor (GPtrArray *files)
+{
+  GFile *ancestor;
+
+  if (files->len == 0)
+    return NULL;
+
+  ancestor = g_file_get_parent (g_ptr_array_index (files, 0));
+
+  for (guint i = 1; i < files->len; i++)
+    {
+      GFile *file = g_ptr_array_index (files, i);
+
+      while (!g_file_has_prefix (file, ancestor))
+        {
+          GFile *old = ancestor;
+          ancestor = g_file_get_parent (old);
+          if (g_file_equal (ancestor, old))
+            break;
+          g_object_unref (old);
+        }
+    }
+
+  return g_steal_pointer (&ancestor);
+}
+
+static GFile *
+get_common_ancestor_array (GFile **files,
+                           gint    n_files)
+{
+  g_autoptr (GPtrArray) fileptrs = g_ptr_array_sized_new (n_files);
+
+  g_assert (files != NULL || n_files == 0);
+
+  for (guint i = 0; i < n_files; i++)
+    g_ptr_array_add (fileptrs, files[i]);
+
+  return get_common_ancestor (fileptrs);
+}
+
+static void
+gbp_editorui_application_addin_add_option_entries (IdeApplicationAddin *addin,
+                                                 IdeApplication      *app)
+{
+  g_assert (IDE_IS_APPLICATION_ADDIN (addin));
+  g_assert (G_IS_APPLICATION (app));
+
+  g_application_add_main_option (G_APPLICATION (app),
+                                 "editor",
+                                 'e',
+                                 G_OPTION_FLAG_IN_MAIN,
+                                 G_OPTION_ARG_NONE,
+                                 _("Use minimal editorui interface"),
+                                 NULL);
+}
+
+static void
+gbp_editorui_application_addin_open_all_cb (GObject      *object,
+                                          GAsyncResult *result,
+                                          gpointer      user_data)
+{
+  IdeWorkbench *workbench = (IdeWorkbench *) object;
+  g_autoptr(GApplicationCommandLine) cmdline = user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_WORKBENCH (workbench));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (!cmdline || G_IS_APPLICATION_COMMAND_LINE (cmdline));
+
+  if (!ide_workbench_open_finish (workbench, result, &error))
+    {
+      if (error != NULL && cmdline != NULL)
+        g_application_command_line_printerr (cmdline, "%s\n", error->message);
+    }
+
+  if (cmdline != NULL)
+    g_application_command_line_set_exit_status (cmdline, error == NULL ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+static void
+gbp_editorui_application_addin_handle_command_line (IdeApplicationAddin     *addin,
+                                                  IdeApplication          *application,
+                                                  GApplicationCommandLine *cmdline)
+{
+  g_autoptr(IdeWorkbench) workbench = NULL;
+  IdeApplication *app = (IdeApplication *)application;
+  g_autoptr(GPtrArray) files = NULL;
+  g_autoptr(GFile) workdir = NULL;
+  g_auto(GStrv) argv = NULL;
+  GVariantDict *options;
+  gint argc;
+
+  g_assert (IDE_IS_APPLICATION_ADDIN (addin));
+  g_assert (IDE_IS_APPLICATION (app));
+  g_assert (G_IS_APPLICATION_COMMAND_LINE (cmdline));
+
+  argv = g_application_command_line_get_arguments (cmdline, &argc);
+
+  if ((options = g_application_command_line_get_options_dict (cmdline)) &&
+      g_variant_dict_contains (options, "editor"))
+    {
+      ide_application_set_workspace_type (application, IDE_TYPE_EDITOR_WORKSPACE);
+
+      /* Just open the editor workspace if no files were specified */
+      if (argc < 2)
+        {
+          IdeEditorWorkspace *workspace;
+          IdeContext *context;
+
+          workdir = g_application_command_line_create_file_for_arg (cmdline, ".");
+          ide_application_set_command_line_handled (application, cmdline, TRUE);
+
+          workbench = ide_workbench_new ();
+          ide_application_add_workbench (app, workbench);
+
+          context = ide_workbench_get_context (workbench);
+          ide_context_set_workdir (context, workdir);
+
+          workspace = ide_editor_workspace_new (application);
+          ide_workbench_add_workspace (workbench, IDE_WORKSPACE (workspace));
+
+          ide_workbench_focus_workspace (workbench, IDE_WORKSPACE (workspace));
+
+          return;
+        }
+    }
+
+  if (argc < 2)
+    return;
+
+  /*
+   * If the user is trying to open various files using the command line with
+   * something like "gnome-builder x.c y.c z.c" then instead of opening the
+   * full project system, we'll open a simplified editor workspace for just
+   * these files and avoid loading a project altogether. That means that they
+   * wont get all of the IDE experience, but its faster to get quick editing
+   * done and then exit.
+   */
+
+  files = g_ptr_array_new_with_free_func (g_object_unref);
+  for (guint i = 1; i < argc; i++)
+    g_ptr_array_add (files,
+                     g_application_command_line_create_file_for_arg (cmdline, argv[i]));
+
+  /* If we find an existing workbench that is an ancestor, or equal to the
+   * common ancestor, then we'll re-use it instead of creating a new one.
+   */
+  workdir = get_common_ancestor (files);
+  if (!(workbench = find_workbench_for_dir (application, workdir)))
+    {
+      IdeEditorWorkspace *workspace;
+      IdeContext *context;
+
+      workbench = ide_workbench_new ();
+      ide_application_add_workbench (app, workbench);
+
+      context = ide_workbench_get_context (workbench);
+
+      /* Setup the working directory to top-most common ancestor of the
+       * files. That way we can still get somewhat localized search results
+       * and other workspace features.
+       */
+      if (workdir != NULL)
+        ide_context_set_workdir (context, workdir);
+
+      workspace = ide_editor_workspace_new (app);
+      ide_workbench_add_workspace (workbench, IDE_WORKSPACE (workspace));
+
+      ide_workbench_focus_workspace (workbench, IDE_WORKSPACE (workspace));
+    }
+
+  g_assert (files->len > 0);
+
+  ide_workbench_open_all_async (workbench,
+                                (GFile **)(gpointer)files->pdata,
+                                files->len,
+                                "editorui",
+                                NULL,
+                                gbp_editorui_application_addin_open_all_cb,
+                                g_object_ref (cmdline));
+}
+
+static void
+gbp_editorui_application_addin_open (IdeApplicationAddin  *addin,
+                                     IdeApplication       *application,
+                                     GFile               **files,
+                                     gint                  n_files,
+                                     const gchar          *hint)
+{
+  g_autoptr(IdeWorkbench) workbench = NULL;
+  g_autoptr(GFile) workdir = NULL;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_APPLICATION (application));
+  g_assert (files != NULL);
+  g_assert (n_files > 0);
+
+  workdir = get_common_ancestor_array (files, n_files);
+
+  if (!(workbench = find_workbench_for_dir (application, workdir)))
+    {
+      IdeEditorWorkspace *workspace;
+      IdeContext *context;
+
+      workbench = ide_workbench_new ();
+      ide_application_add_workbench (application, workbench);
+
+      context = ide_workbench_get_context (workbench);
+
+      /* Setup the working directory to top-most common ancestor of the
+       * files. That way we can still get somewhat localized search results
+       * and other workspace features.
+       */
+      if (workdir != NULL)
+        ide_context_set_workdir (context, workdir);
+
+      workspace = ide_editor_workspace_new (application);
+      ide_workbench_add_workspace (workbench, IDE_WORKSPACE (workspace));
+
+      ide_workbench_focus_workspace (workbench, IDE_WORKSPACE (workspace));
+    }
+
+  ide_workbench_open_all_async (workbench,
+                                files,
+                                n_files,
+                                "editorui",
+                                NULL,
+                                gbp_editorui_application_addin_open_all_cb,
+                                NULL);
+
+}
+
+static void
+new_editor_workspace_action (GSimpleAction *action,
+                             GVariant      *param,
+                             gpointer       user_data)
+{
+  g_autoptr(IdeWorkbench) workbench = NULL;
+  g_autoptr(GFile) workdir = NULL;
+  IdeEditorWorkspace *workspace;
+  IdeContext *context;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_EDITORUI_APPLICATION_ADDIN (user_data));
+
+  workbench = ide_workbench_new ();
+  ide_application_add_workbench (IDE_APPLICATION_DEFAULT, workbench);
+
+  context = ide_workbench_get_context (workbench);
+  workdir = g_file_new_for_path (ide_get_projects_dir ());
+  ide_context_set_workdir (context, workdir);
+
+  workspace = ide_editor_workspace_new (IDE_APPLICATION_DEFAULT);
+  ide_workbench_add_workspace (workbench, IDE_WORKSPACE (workspace));
+
+  ide_workbench_focus_workspace (workbench, IDE_WORKSPACE (workspace));
+}
+
+static void
+update_menus (IdeApplication *app)
+{
+  g_autoptr(GMenuItem) lf = NULL;
+  const char *lf_name = NULL;
+  GMenu *menu;
+
+  g_assert (IDE_IS_APPLICATION (app));
+
+#if defined(G_OS_UNIX)
+# if defined(__APPLE__)
+  lf_name = "macOS (LF)";
+# elif  defined(__linux__)
+  lf_name = "Linux (LF)";
+# else
+  lf_name = "Unix (LF)";
+# endif
+#else
+  /* G_OS_WIN32 */
+  lf_name = "Linux (LF)";
+#endif
+
+  g_assert (lf_name != NULL);
+
+  menu = ide_application_get_menu_by_id (app, "editorui-line-ends-section");
+  lf = g_menu_item_new (lf_name, NULL);
+  g_menu_item_set_action_and_target (lf, "editorui.newline-type", "s", "lf");
+  g_menu_prepend_item (menu, lf);
+}
+
+static GActionEntry actions[] = {
+  { "new-editor-workspace", new_editor_workspace_action },
+};
+
+static void
+gbp_editorui_application_addin_load (IdeApplicationAddin *addin,
+                                     IdeApplication      *application)
+{
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_APPLICATION_ADDIN (addin));
+  g_assert (IDE_IS_APPLICATION (application));
+
+  g_action_map_add_action_entries (G_ACTION_MAP (application),
+                                   actions,
+                                   G_N_ELEMENTS (actions),
+                                   addin);
+
+  update_menus (application);
+}
+
+static void
+gbp_editorui_application_addin_unload (IdeApplicationAddin *addin,
+                                       IdeApplication      *application)
+{
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_APPLICATION_ADDIN (addin));
+  g_assert (IDE_IS_APPLICATION (application));
+
+  for (guint i = 0; i < G_N_ELEMENTS (actions); i++)
+    g_action_map_remove_action (G_ACTION_MAP (application), actions[i].name);
+}
+
+static void
+cmdline_addin_iface_init (IdeApplicationAddinInterface *iface)
+{
+  iface->add_option_entries = gbp_editorui_application_addin_add_option_entries;
+  iface->handle_command_line = gbp_editorui_application_addin_handle_command_line;
+  iface->open = gbp_editorui_application_addin_open;
+  iface->load = gbp_editorui_application_addin_load;
+  iface->unload = gbp_editorui_application_addin_unload;
+}
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (GbpEditoruiApplicationAddin, gbp_editorui_application_addin, G_TYPE_OBJECT,
+                               G_IMPLEMENT_INTERFACE (IDE_TYPE_APPLICATION_ADDIN, cmdline_addin_iface_init))
+
+static void
+gbp_editorui_application_addin_class_init (GbpEditoruiApplicationAddinClass *klass)
+{
+}
+
+static void
+gbp_editorui_application_addin_init (GbpEditoruiApplicationAddin *self)
+{
+}
diff --git a/src/plugins/editorui/gbp-editorui-application-addin.h 
b/src/plugins/editorui/gbp-editorui-application-addin.h
new file mode 100644
index 000000000..bbd71f5ab
--- /dev/null
+++ b/src/plugins/editorui/gbp-editorui-application-addin.h
@@ -0,0 +1,31 @@
+/* gbp-editorui-application-addin.h
+ *
+ * Copyright 2018-2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-core.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_EDITORUI_APPLICATION_ADDIN (gbp_editorui_application_addin_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpEditoruiApplicationAddin, gbp_editorui_application_addin, GBP, 
EDITORUI_APPLICATION_ADDIN, GObject)
+
+G_END_DECLS
diff --git a/src/plugins/editorui/gbp-editorui-position-label.c 
b/src/plugins/editorui/gbp-editorui-position-label.c
new file mode 100644
index 000000000..be65ba1d9
--- /dev/null
+++ b/src/plugins/editorui/gbp-editorui-position-label.c
@@ -0,0 +1,79 @@
+/* gbp-editorui-position-label.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-editorui-position-label"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "gbp-editorui-position-label.h"
+
+struct _GbpEditoruiPositionLabel
+{
+  GtkWidget parent_instance;
+  GtkLabel *label;
+};
+
+G_DEFINE_TYPE (GbpEditoruiPositionLabel, gbp_editorui_position_label, GTK_TYPE_WIDGET)
+
+static void
+gbp_editorui_position_label_dispose (GObject *object)
+{
+  GbpEditoruiPositionLabel *self = (GbpEditoruiPositionLabel *)object;
+
+  gtk_widget_unparent (GTK_WIDGET (self->label));
+  self->label = NULL;
+
+  G_OBJECT_CLASS (gbp_editorui_position_label_parent_class)->dispose (object);
+}
+
+static void
+gbp_editorui_position_label_class_init (GbpEditoruiPositionLabelClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->dispose = gbp_editorui_position_label_dispose;
+
+  gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/plugins/editorui/gbp-editorui-position-label.ui");
+  gtk_widget_class_bind_template_child (widget_class, GbpEditoruiPositionLabel, label);
+}
+
+static void
+gbp_editorui_position_label_init (GbpEditoruiPositionLabel *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+void
+gbp_editorui_position_label_update (GbpEditoruiPositionLabel *self,
+                                    guint                     line,
+                                    guint                     column)
+{
+  char str[64];
+
+  g_return_if_fail (GBP_IS_EDITORUI_POSITION_LABEL (self));
+
+  /* translators: the first %u is replaced with the line number and the second with the column. */
+  g_snprintf (str, sizeof str, _("Ln %u, Col %u"), line + 1, column + 1);
+  gtk_label_set_label (self->label, str);
+}
diff --git a/src/plugins/editorui/gbp-editorui-position-label.h 
b/src/plugins/editorui/gbp-editorui-position-label.h
new file mode 100644
index 000000000..6013ec41e
--- /dev/null
+++ b/src/plugins/editorui/gbp-editorui-position-label.h
@@ -0,0 +1,35 @@
+/* gbp-editorui-position-label.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_EDITORUI_POSITION_LABEL (gbp_editorui_position_label_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpEditoruiPositionLabel, gbp_editorui_position_label, GBP, EDITORUI_POSITION_LABEL, 
GtkWidget)
+
+void gbp_editorui_position_label_update (GbpEditoruiPositionLabel *self,
+                                         guint                     line,
+                                         guint                     column);
+
+G_END_DECLS
diff --git a/src/plugins/editorui/gbp-editorui-position-label.ui 
b/src/plugins/editorui/gbp-editorui-position-label.ui
new file mode 100644
index 000000000..26b2c90da
--- /dev/null
+++ b/src/plugins/editorui/gbp-editorui-position-label.ui
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GbpEditoruiPositionLabel" parent="GtkWidget">
+    <child>
+      <object class="GtkLabel" id="label">
+        <property name="width-chars">12</property>
+        <attributes>
+          <attribute name="font-features" value="tnum"/>
+        </attributes>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/plugins/editorui/gbp-editorui-preferences-addin.c 
b/src/plugins/editorui/gbp-editorui-preferences-addin.c
new file mode 100644
index 000000000..f662451b9
--- /dev/null
+++ b/src/plugins/editorui/gbp-editorui-preferences-addin.c
@@ -0,0 +1,461 @@
+/* gbp-editorui-preferences-addin.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-editorui-preferences-addin"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include <libide-sourceview.h>
+
+#include "gbp-editorui-preferences-addin.h"
+#include "gbp-editorui-preview.h"
+
+#define LANG_PATH "/org/gnome/builder/editor/language/*"
+
+struct _GbpEditoruiPreferencesAddin
+{
+  GObject parent_instance;
+};
+
+static const IdePreferenceGroupEntry groups[] = {
+  { "appearance", "preview",      10, N_("Style") },
+  { "appearance", "schemes",      20, NULL },
+  { "appearance", "font",         30, NULL },
+  { "appearance", "effects",      40, NULL },
+  { "appearance", "lines",        50, NULL },
+  { "appearance", "brackets",     60, NULL },
+  { "appearance", "accessories", 100, NULL },
+
+  { "keyboard",   "movement",      10, N_("Movements") },
+};
+
+static const IdePreferenceGroupEntry lang_groups[] = {
+  { "languages/*", "general",      0, N_("General") },
+  { "languages/*", "margins",     10, N_("Margins") },
+  { "languages/*", "spacing",     20, N_("Spacing") },
+  { "languages/*", "indentation", 30, N_("Indentation") },
+};
+
+static const IdePreferenceItemEntry items[] = {
+  { "appearance", "font", "font-name", 0, ide_preferences_window_font,
+    N_("Editor Font"),
+    N_("The font used within the source code editor"),
+    "org.gnome.builder.editor", NULL, "font-name" },
+
+  { "appearance", "effects", "show-grid-lines", 10, ide_preferences_window_toggle,
+    N_("Show Grid Pattern"),
+    N_("Display a grid pattern underneath source code"),
+    "org.gnome.builder.editor", NULL, "show-grid-lines" },
+
+  { "appearance", "effects", "show-map", 10, ide_preferences_window_toggle,
+    N_("Show Overview Map"),
+    N_("Use an overview map instead of a scrollbar"),
+    "org.gnome.builder.editor", NULL, "show-map" },
+
+  { "appearance", "lines", "show-line-numbers", 0, ide_preferences_window_toggle,
+    N_("Show Line Numbers"),
+    N_("Display line numbers next to each line of source code"),
+    "org.gnome.builder.editor", NULL, "show-line-numbers" },
+
+  { "appearance", "lines", "line-height", 0, ide_preferences_window_spin,
+    N_("Line Height"),
+    N_("Adjust line-height of the configured font"),
+    "org.gnome.builder.editor", NULL, "line-height" },
+
+  { "appearance", "lines", "highlight-current-line", 20, ide_preferences_window_toggle,
+    N_("Highlight Current Line"),
+    N_("Make current line stand out with highlights"),
+    "org.gnome.builder.editor", NULL, "highlight-current-line" },
+
+  { "appearance", "brackets", "highlight-matching-brackets", 30, ide_preferences_window_toggle,
+    N_("Highlight Matching Brackets"),
+    N_("Use cursor position to highlight matching brackets, braces, parenthesis, and more"),
+    "org.gnome.builder.editor", NULL, "highlight-matching-brackets" },
+
+  { "editing", "completion", "interactive-completion", 10, ide_preferences_window_toggle,
+    N_("Suggest Completions While Typing"),
+    N_("Automatically suggest completions while typing within the file"),
+    "org.gnome.builder.editor", NULL, "interactive-completion" },
+
+  { "editing", "completion", "select-first-completion", 20, ide_preferences_window_toggle,
+    N_("Select First Completion"),
+    N_("Automatically select the first completion when displayed"),
+    "org.gnome.builder.editor", NULL, "select-first-completion" },
+
+  { "editing", "snippets", "enable-snippets", 0, ide_preferences_window_toggle,
+    N_("Expand Snippets"),
+    N_("Use “Tab” to expand configured snippets in the editor"),
+    "org.gnome.builder.editor", NULL, "enable-snippets" },
+
+  { "keyboard", "movement", "smart-home-end", 0, ide_preferences_window_toggle,
+    N_("Smart Home and End"),
+    N_("Home moves to first non-whitespace character"),
+    "org.gnome.builder.editor", NULL, "smart-home-end" },
+
+  { "keyboard", "movement", "smart-backspace", 0, ide_preferences_window_toggle,
+    N_("Smart Backspace"),
+    N_("Backspace will remove extra space to keep you aligned with your indentation"),
+    "org.gnome.builder.editor", NULL, "smart-backspace" },
+};
+
+static const IdePreferenceItemEntry lang_items[] = {
+  { "languages/*", "general", "trim", 0, ide_preferences_window_toggle,
+    N_("Trim Trailing Whitespace"),
+    N_("Upon saving, trailing whitepsace from modified lines will be trimmed"),
+    "org.gnome.builder.editor.language", LANG_PATH, "trim-trailing-whitespace" },
+
+  { "languages/*", "general", "overwrite", 0, ide_preferences_window_toggle,
+    N_("Overwrite Braces"),
+    N_("Overwrite closing braces"),
+    "org.gnome.builder.editor.language", LANG_PATH, "overwrite-braces" },
+
+  { "languages/*", "general", "insert-matching", 0, ide_preferences_window_toggle,
+    N_("Insert Matching Brace"),
+    N_("Insert matching character for [[(\"'"),
+    "org.gnome.builder.editor.language", LANG_PATH, "insert-matching-brace" },
+
+  { "languages/*", "general", "insert-trailing", 0, ide_preferences_window_toggle,
+    N_("Insert Trailing Newline"),
+    N_("Ensure files end with a newline"),
+    "org.gnome.builder.editor.language", LANG_PATH, "insert-trailing-newline" },
+
+  { "languages/*", "margins", "show-right-margin", 0, ide_preferences_window_toggle,
+    N_("Show right margin"),
+    N_("Display a margin in the editor to indicate maximum desired width"),
+    "org.gnome.builder.editor.language", LANG_PATH, "show-right-margin" },
+
+#if 0
+  { "languages/*", "spacing", "before-parens", 0, ide_preferences_window_toggle, "Prefer a space before 
opening parentheses" },
+  { "languages/*", "spacing", "before-brackets", 0, ide_preferences_window_toggle, "Prefer a space before 
opening brackets" },
+  { "languages/*", "spacing", "before-braces", 0, ide_preferences_window_toggle, "Prefer a space before 
opening braces" },
+  { "languages/*", "spacing", "before-angles", 0, ide_preferences_window_toggle, "Prefer a space before 
opening angles" },
+#endif
+
+  { "languages/*", "indentation", "insert-spaces", 0, ide_preferences_window_toggle,
+    N_("Insert spaces instead of tabs"),
+    N_("Prefer spaces over tabs"),
+    "org.gnome.builder.editor.language", LANG_PATH, "insert-spaces-instead-of-tabs" },
+
+  { "languages/*", "indentation", "auto-indent", 0, ide_preferences_window_toggle,
+    N_("Automatically Indent"),
+    N_("Format source code as you type"),
+    "org.gnome.builder.editor.language", LANG_PATH, "auto-indent" },
+};
+
+
+static int
+compare_section (gconstpointer a,
+                 gconstpointer b)
+{
+  const IdePreferencePageEntry *pagea = a;
+  const IdePreferencePageEntry *pageb = b;
+
+  return g_strcmp0 (pagea->section, pageb->section);
+}
+
+static void
+notify_style_scheme_cb (IdeApplication *app,
+                        GParamSpec     *pspec,
+                        GtkFlowBox     *flowbox)
+{
+  const char *style_scheme;
+  gboolean dark;
+
+  g_assert (IDE_IS_APPLICATION (app));
+  g_assert (GTK_IS_FLOW_BOX (flowbox));
+
+  style_scheme = ide_application_get_style_scheme (app);
+  dark = ide_application_get_dark (app);
+
+  for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (flowbox));
+       child != NULL;
+       child = gtk_widget_get_next_sibling (child))
+    {
+      GtkSourceStyleSchemePreview *preview = GTK_SOURCE_STYLE_SCHEME_PREVIEW (gtk_flow_box_child_get_child 
(GTK_FLOW_BOX_CHILD (child)));
+      GtkSourceStyleScheme *scheme = gtk_source_style_scheme_preview_get_scheme (preview);
+      gboolean visible = dark == ide_source_style_scheme_is_dark (scheme);
+      gboolean selected = g_strcmp0 (style_scheme, gtk_source_style_scheme_get_id (scheme)) == 0;
+
+      gtk_source_style_scheme_preview_set_selected (preview, selected);
+      gtk_widget_set_visible (child, visible);
+    }
+}
+
+static gboolean
+can_install_scheme (GtkSourceStyleSchemeManager *manager,
+                    const char * const          *scheme_ids,
+                    GFile                       *file)
+{
+  g_autofree char *uri = NULL;
+  const char *path;
+
+  g_assert (GTK_SOURCE_IS_STYLE_SCHEME_MANAGER (manager));
+  g_assert (G_IS_FILE (file));
+
+  uri = g_file_get_uri (file);
+
+  /* Don't allow resources, which would be weird anyway */
+  if (g_str_has_prefix (uri, "resource://"))
+    return FALSE;
+
+  /* Make sure it's in the form of name.xml as we will require
+   * that elsewhere anyway.
+   */
+  if (!g_str_has_suffix (uri, ".xml"))
+    return FALSE;
+
+  /* Not a native file, so likely not already installed */
+  if (!g_file_is_native (file))
+    return TRUE;
+
+  path = g_file_peek_path (file);
+  scheme_ids = gtk_source_style_scheme_manager_get_scheme_ids (manager);
+  for (guint i = 0; scheme_ids[i] != NULL; i++)
+    {
+      GtkSourceStyleScheme *scheme = gtk_source_style_scheme_manager_get_scheme (manager, scheme_ids[i]);
+      const char *filename = gtk_source_style_scheme_get_filename (scheme);
+
+      /* If we have already loaded this scheme, then ignore it */
+      if (g_strcmp0 (filename, path) == 0)
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+drop_scheme_cb (GtkDropTarget *drop_target,
+                const GValue  *value,
+                double         x,
+                double         y,
+                gpointer       user_data)
+{
+  g_assert (GTK_IS_DROP_TARGET (drop_target));
+
+  if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
+    {
+      GSList *list = g_value_get_boxed (value);
+      g_autoptr(GPtrArray) to_install = NULL;
+      GtkSourceStyleSchemeManager *manager;
+      const char * const *scheme_ids;
+
+      if (list == NULL)
+        return FALSE;
+
+      manager = gtk_source_style_scheme_manager_get_default ();
+      scheme_ids = gtk_source_style_scheme_manager_get_scheme_ids (manager);
+      to_install = g_ptr_array_new_with_free_func (g_object_unref);
+
+      for (const GSList *iter = list; iter; iter = iter->next)
+        {
+          GFile *file = iter->data;
+
+          if (can_install_scheme (manager, scheme_ids, file))
+            g_ptr_array_add (to_install, g_object_ref (file));
+        }
+
+      if (to_install->len == 0)
+        return FALSE;
+
+      /* TODO: We need to reload the preferences */
+      ide_application_install_schemes_async (IDE_APPLICATION_DEFAULT,
+                                             (GFile **)(gpointer)to_install->pdata,
+                                             to_install->len,
+                                             NULL, NULL, NULL);
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+ide_preferences_builtin_add_schemes (const char                   *page_name,
+                                     const IdePreferenceItemEntry *entry,
+                                     AdwPreferencesGroup          *group,
+                                     gpointer                      user_data)
+{
+  IdePreferencesWindow *window = user_data;
+  GtkSourceStyleSchemeManager *manager;
+  const char * const *scheme_ids;
+  GtkDropTarget *drop_target;
+  GtkFlowBox *flowbox;
+  GtkWidget *preview;
+
+  g_assert (IDE_IS_PREFERENCES_WINDOW (window));
+  g_assert (entry != NULL);
+  g_assert (ADW_IS_PREFERENCES_GROUP (group));
+
+  preview = gbp_editorui_preview_new ();
+  gtk_widget_add_css_class (GTK_WIDGET (preview), "card");
+  gtk_widget_set_margin_bottom (preview, 12);
+  adw_preferences_group_add (group, preview);
+
+  manager = gtk_source_style_scheme_manager_get_default ();
+  scheme_ids = gtk_source_style_scheme_manager_get_scheme_ids (manager);
+
+  flowbox = g_object_new (GTK_TYPE_FLOW_BOX,
+                          "activate-on-single-click", TRUE,
+                          "column-spacing", 12,
+                          "row-spacing", 12,
+                          "margin-top", 6,
+                          "max-children-per-line", 4,
+                          NULL);
+  gtk_widget_add_css_class (GTK_WIDGET (flowbox), "style-schemes");
+
+  /* Setup DnD for schemes to be dropped onto the section */
+  drop_target = gtk_drop_target_new (GDK_TYPE_FILE_LIST, GDK_ACTION_COPY);
+  g_signal_connect (drop_target,
+                    "drop",
+                    G_CALLBACK (drop_scheme_cb),
+                    NULL);
+  gtk_widget_add_controller (GTK_WIDGET (flowbox), GTK_EVENT_CONTROLLER (drop_target));
+
+  for (guint i = 0; scheme_ids[i]; i++)
+    {
+      GtkSourceStyleScheme *scheme = gtk_source_style_scheme_manager_get_scheme (manager, scheme_ids[i]);
+      GtkSourceStyleSchemePreview *selector;
+
+      selector = g_object_new (GTK_SOURCE_TYPE_STYLE_SCHEME_PREVIEW,
+                               "action-name", "app.style-scheme-name",
+                               "scheme", scheme,
+                               NULL);
+      gtk_actionable_set_action_target (GTK_ACTIONABLE (selector), "s", scheme_ids[i]);
+      gtk_flow_box_append (flowbox, GTK_WIDGET (selector));
+    }
+
+  g_signal_connect_object (IDE_APPLICATION_DEFAULT,
+                           "notify::style-scheme",
+                           G_CALLBACK (notify_style_scheme_cb),
+                           flowbox,
+                           0);
+  notify_style_scheme_cb (IDE_APPLICATION_DEFAULT, NULL, flowbox);
+
+  adw_preferences_group_add (group, GTK_WIDGET (flowbox));
+}
+
+static void
+gbp_editorui_preferences_addin_add_languages (IdePreferencesWindow *window,
+                                              const char           *lang_path)
+{
+  GtkSourceLanguageManager *langs;
+  const char * const *lang_ids;
+  IdePreferencePageEntry *lpages;
+  IdePreferenceItemEntry _items[G_N_ELEMENTS (lang_items)];
+  guint j = 0;
+
+  g_assert (IDE_IS_PREFERENCES_WINDOW (window));
+
+  langs = gtk_source_language_manager_get_default ();
+  lang_ids = gtk_source_language_manager_get_language_ids (langs);
+  lpages = g_new0 (IdePreferencePageEntry, g_strv_length ((char **)lang_ids));
+
+  for (guint i = 0; lang_ids[i]; i++)
+    {
+      GtkSourceLanguage *l = gtk_source_language_manager_get_language (langs, lang_ids[i]);
+      IdePreferencePageEntry *page;
+      char name[256];
+
+      if (gtk_source_language_get_hidden (l))
+        continue;
+
+      page = &lpages[j++];
+
+      g_snprintf (name, sizeof name, "languages/%s", lang_ids[i]);
+
+      page->parent = "languages";
+      page->section = gtk_source_language_get_section (l);
+      page->name = g_intern_string (name);
+      page->icon_name = NULL;
+      page->title = gtk_source_language_get_name (l);
+    }
+
+  qsort (lpages, j, sizeof *lpages, compare_section);
+  for (guint i = 0; i < j; i++)
+    lpages[i].priority = i;
+
+  memcpy (_items, lang_items, sizeof _items);
+  for (guint i = 0; i < G_N_ELEMENTS (_items); i++)
+    _items[i].path = lang_path;
+
+  ide_preferences_window_add_pages (window, lpages, j, NULL);
+  ide_preferences_window_add_groups (window, lang_groups, G_N_ELEMENTS (lang_groups), NULL);
+  ide_preferences_window_add_items (window, _items, G_N_ELEMENTS (_items), window, NULL);
+
+  g_free (lpages);
+}
+
+
+static void
+gbp_editorui_preferences_addin_load (IdePreferencesAddin  *addin,
+                                     IdePreferencesWindow *window,
+                                     IdeContext           *context)
+{
+  GbpEditoruiPreferencesAddin *self = (GbpEditoruiPreferencesAddin *)addin;
+  IdePreferencesMode mode;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_EDITORUI_PREFERENCES_ADDIN (self));
+  g_assert (IDE_IS_PREFERENCES_WINDOW (window));
+  g_assert (!context || IDE_IS_CONTEXT (context));
+
+  mode = ide_preferences_window_get_mode (window);
+
+  if (mode == IDE_PREFERENCES_MODE_APPLICATION)
+    {
+      ide_preferences_window_add_groups (window, groups, G_N_ELEMENTS (groups), NULL);
+      ide_preferences_window_add_items (window, items, G_N_ELEMENTS (items), window, NULL);
+      ide_preferences_window_add_item (window, "appearance", "preview", "scheme", 0,
+                                       ide_preferences_builtin_add_schemes, window, NULL);
+      gbp_editorui_preferences_addin_add_languages (window, LANG_PATH);
+    }
+  else if (mode == IDE_PREFERENCES_MODE_PROJECT && IDE_IS_CONTEXT (context))
+    {
+      g_autofree char *project_id = ide_context_dup_project_id (context);
+      g_autofree char *project_lang_path = g_strdup_printf ("/org/gnome/builder/projects/%s/language/*", 
project_id);
+
+      gbp_editorui_preferences_addin_add_languages (window, project_lang_path);
+    }
+
+  IDE_EXIT;
+}
+
+static void
+preferences_addin_init (IdePreferencesAddinInterface *iface)
+{
+  iface->load = gbp_editorui_preferences_addin_load;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GbpEditoruiPreferencesAddin, gbp_editorui_preferences_addin, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (IDE_TYPE_PREFERENCES_ADDIN, preferences_addin_init))
+
+static void
+gbp_editorui_preferences_addin_class_init (GbpEditoruiPreferencesAddinClass *klass)
+{
+}
+
+static void
+gbp_editorui_preferences_addin_init (GbpEditoruiPreferencesAddin *self)
+{
+}
diff --git a/src/plugins/editorui/gbp-editorui-preferences-addin.h 
b/src/plugins/editorui/gbp-editorui-preferences-addin.h
new file mode 100644
index 000000000..de149f881
--- /dev/null
+++ b/src/plugins/editorui/gbp-editorui-preferences-addin.h
@@ -0,0 +1,31 @@
+/* gbp-editorui-preferences-addin.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-gui.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_EDITORUI_PREFERENCES_ADDIN (gbp_editorui_preferences_addin_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpEditoruiPreferencesAddin, gbp_editorui_preferences_addin, GBP, 
EDITORUI_PREFERENCES_ADDIN, GObject)
+
+G_END_DECLS
diff --git a/src/plugins/editorui/gbp-editorui-preview.c b/src/plugins/editorui/gbp-editorui-preview.c
new file mode 100644
index 000000000..62e0c8919
--- /dev/null
+++ b/src/plugins/editorui/gbp-editorui-preview.c
@@ -0,0 +1,173 @@
+/* gbp-editorui-preview.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-editorui-preview"
+
+#include "config.h"
+
+#include <libide-gui.h>
+
+#include "gbp-editorui-preview.h"
+
+struct _GbpEditoruiPreview
+{
+  GtkSourceView parent_instance;
+  GSettings *editor_settings;
+};
+
+G_DEFINE_TYPE (GbpEditoruiPreview, gbp_editorui_preview, GTK_SOURCE_TYPE_VIEW)
+
+static void
+gbp_editorui_preview_load_text (GbpEditoruiPreview *self)
+{
+  GtkSourceLanguageManager *manager;
+  GtkSourceLanguage *lang;
+  GtkTextBuffer *buffer;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_EDITORUI_PREVIEW (self));
+
+  manager = gtk_source_language_manager_get_default ();
+  lang = gtk_source_language_manager_get_language (manager, "c");
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
+  gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (buffer), lang);
+  gtk_text_buffer_set_text (buffer, "\
+#include <glib.h>\n\
+typedef struct _type_t type_t;\n\
+type_t *type_new (int id);\n\
+void type_free (type_t *t);\
+", -1);
+
+  IDE_EXIT;
+}
+
+static void
+notify_style_scheme_cb (GbpEditoruiPreview *self,
+                        GParamSpec         *pspec,
+                        IdeApplication     *app)
+{
+  GtkSourceStyleSchemeManager *manager;
+  GtkSourceStyleScheme *scheme;
+  GtkTextBuffer *buffer;
+  const char *name;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_EDITORUI_PREVIEW (self));
+  g_assert (IDE_IS_APPLICATION (app));
+
+  name = ide_application_get_style_scheme (app);
+  manager = gtk_source_style_scheme_manager_get_default ();
+  scheme = gtk_source_style_scheme_manager_get_scheme (manager, name);
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
+
+  gtk_source_buffer_set_style_scheme (GTK_SOURCE_BUFFER (buffer), scheme);
+
+  IDE_EXIT;
+}
+
+static gboolean
+show_grid_lines_to_bg (GValue   *value,
+                       GVariant *variant,
+                       gpointer  user_data)
+{
+  if (g_variant_get_boolean (variant))
+    g_value_set_enum (value, GTK_SOURCE_BACKGROUND_PATTERN_TYPE_GRID);
+  else
+    g_value_set_enum (value, GTK_SOURCE_BACKGROUND_PATTERN_TYPE_NONE);
+  return TRUE;
+}
+
+static void
+gbp_editorui_preview_constructed (GObject *object)
+{
+  GbpEditoruiPreview *self = (GbpEditoruiPreview *)object;
+  GtkTextBuffer *buffer;
+
+  G_OBJECT_CLASS (gbp_editorui_preview_parent_class)->constructed (object);
+
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
+
+  g_signal_connect_object (IDE_APPLICATION_DEFAULT,
+                           "notify::style-scheme",
+                           G_CALLBACK (notify_style_scheme_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  notify_style_scheme_cb (self, NULL, IDE_APPLICATION_DEFAULT);
+
+  g_settings_bind_with_mapping (self->editor_settings,
+                                "show-grid-lines", self, "background-pattern",
+                                G_SETTINGS_BIND_GET,
+                                show_grid_lines_to_bg, NULL, NULL, NULL);
+  g_settings_bind (self->editor_settings,
+                   "highlight-current-line", self, "highlight-current-line",
+                   G_SETTINGS_BIND_GET);
+  g_settings_bind (self->editor_settings,
+                   "highlight-matching-brackets", buffer, "highlight-matching-brackets",
+                   G_SETTINGS_BIND_GET);
+  g_settings_bind (self->editor_settings,
+                   "show-line-numbers", self, "show-line-numbers",
+                   G_SETTINGS_BIND_GET);
+
+  gbp_editorui_preview_load_text (self);
+}
+
+static void
+gbp_editorui_preview_dispose (GObject *object)
+{
+  GbpEditoruiPreview *self = (GbpEditoruiPreview *)object;
+
+  g_clear_object (&self->editor_settings);
+
+  G_OBJECT_CLASS (gbp_editorui_preview_parent_class)->dispose (object);
+}
+
+static void
+gbp_editorui_preview_class_init (GbpEditoruiPreviewClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed = gbp_editorui_preview_constructed;
+  object_class->dispose = gbp_editorui_preview_dispose;
+}
+
+static void
+gbp_editorui_preview_init (GbpEditoruiPreview *self)
+{
+  self->editor_settings = g_settings_new ("org.gnome.builder.editor");
+
+  gtk_text_view_set_monospace (GTK_TEXT_VIEW (self), TRUE);
+  gtk_source_view_set_show_line_numbers (GTK_SOURCE_VIEW (self), TRUE);
+
+  g_object_set (self,
+                "left-margin", 6,
+                "top-margin", 6,
+                "bottom-margin", 6,
+                "right-margin", 6,
+                NULL);
+}
+
+GtkWidget *
+gbp_editorui_preview_new (void)
+{
+  return g_object_new (GBP_TYPE_EDITORUI_PREVIEW, NULL);
+}
diff --git a/src/plugins/editorui/gbp-editorui-preview.h b/src/plugins/editorui/gbp-editorui-preview.h
new file mode 100644
index 000000000..dbd2d673d
--- /dev/null
+++ b/src/plugins/editorui/gbp-editorui-preview.h
@@ -0,0 +1,33 @@
+/* gbp-editorui-preview.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtksourceview/gtksource.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_EDITORUI_PREVIEW (gbp_editorui_preview_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpEditoruiPreview, gbp_editorui_preview, GBP, EDITORUI_PREVIEW, GtkSourceView)
+
+GtkWidget *gbp_editorui_preview_new (void);
+
+G_END_DECLS
diff --git a/src/plugins/editorui/gbp-editorui-workbench-addin.c 
b/src/plugins/editorui/gbp-editorui-workbench-addin.c
new file mode 100644
index 000000000..576343f2e
--- /dev/null
+++ b/src/plugins/editorui/gbp-editorui-workbench-addin.c
@@ -0,0 +1,304 @@
+/* gbp-editorui-workbench-addin.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-editorui-workbench-addin"
+
+#include "config.h"
+
+#include <libide-editor.h>
+#include <libide-gui.h>
+#include <libide-sourceview.h>
+
+#include "gbp-editorui-workbench-addin.h"
+
+struct _GbpEditoruiWorkbenchAddin
+{
+  GObject       parent_instance;
+  IdeWorkbench *workbench;
+};
+
+typedef struct
+{
+  IdePanelPosition   *position;
+  GFile              *file;
+  IdeBufferOpenFlags  flags;
+  gint                at_line;
+  gint                at_line_offset;
+} OpenFileTaskData;
+
+static GHashTable *overrides;
+
+static void
+open_file_task_data_free (gpointer data)
+{
+  OpenFileTaskData *td = data;
+
+  g_clear_object (&td->file);
+  g_clear_pointer (&td->position, ide_panel_position_unref);
+  g_slice_free (OpenFileTaskData, td);
+}
+
+static void
+gbp_editorui_workbench_addin_load (IdeWorkbenchAddin *addin,
+                                   IdeWorkbench      *workbench)
+{
+  GbpEditoruiWorkbenchAddin *self = (GbpEditoruiWorkbenchAddin *)addin;
+
+  g_assert (GBP_IS_EDITORUI_WORKBENCH_ADDIN (self));
+  g_assert (IDE_IS_WORKBENCH (workbench));
+
+  self->workbench = workbench;
+}
+
+static void
+gbp_editorui_workbench_addin_unload (IdeWorkbenchAddin *addin,
+                                     IdeWorkbench      *workbench)
+{
+  GbpEditoruiWorkbenchAddin *self = (GbpEditoruiWorkbenchAddin *)addin;
+
+  g_assert (GBP_IS_EDITORUI_WORKBENCH_ADDIN (self));
+  g_assert (IDE_IS_WORKBENCH (workbench));
+
+  self->workbench = NULL;
+}
+
+static gboolean
+gbp_editorui_workbench_addin_can_open (IdeWorkbenchAddin *addin,
+                                       GFile             *file,
+                                       const gchar       *content_type,
+                                       gint              *priority)
+{
+  const char *path;
+
+  g_assert (GBP_IS_EDITORUI_WORKBENCH_ADDIN (addin));
+  g_assert (G_IS_FILE (file));
+  g_assert (priority != NULL);
+
+  *priority = 0;
+
+  path = g_file_peek_path (file);
+
+  if (path != NULL || content_type != NULL)
+    {
+      GtkSourceLanguageManager *manager;
+      GtkSourceLanguage *language;
+
+      manager = gtk_source_language_manager_get_default ();
+      language = gtk_source_language_manager_guess_language (manager, path, content_type);
+
+      if (language != NULL)
+        return TRUE;
+    }
+
+  /* Escape hatch in case shared-mime-info fails us */
+  if (path != NULL)
+    {
+      const char *suffix = strrchr (path, '.');
+
+      if (suffix && g_hash_table_contains (overrides, suffix))
+        return TRUE;
+    }
+
+  if (content_type != NULL)
+    {
+      static char *text_plain_type;
+
+      if G_UNLIKELY (text_plain_type == NULL)
+        text_plain_type = g_content_type_from_mime_type ("text/plain");
+
+      if (g_content_type_is_a (content_type, text_plain_type))
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+find_preferred_workspace_cb (IdeWorkspace *workspace,
+                             gpointer      user_data)
+{
+  IdeWorkspace **out_workspace = user_data;
+
+  g_assert (IDE_IS_WORKSPACE (workspace));
+  g_assert (out_workspace != NULL);
+  g_assert (*out_workspace == NULL || IDE_IS_WORKSPACE (*out_workspace));
+
+  if (IDE_IS_PRIMARY_WORKSPACE (workspace))
+    *out_workspace = workspace;
+  else if (*out_workspace == NULL && IDE_IS_EDITOR_WORKSPACE (workspace))
+    *out_workspace = workspace;
+}
+
+static void
+gbp_editorui_workbench_addin_open_cb (GObject      *object,
+                                      GAsyncResult *result,
+                                      gpointer      user_data)
+{
+  IdeBufferManager *buffer_manager = (IdeBufferManager *)object;
+  GbpEditoruiWorkbenchAddin *self;
+  g_autoptr(IdeBuffer) buffer = NULL;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  OpenFileTaskData *state;
+  IdeWorkspace *workspace;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_BUFFER_MANAGER (buffer_manager));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  self = ide_task_get_source_object (task);
+  buffer = ide_buffer_manager_load_file_finish (buffer_manager, result, &error);
+
+  g_assert (GBP_IS_EDITORUI_WORKBENCH_ADDIN (self));
+  g_assert (!buffer || IDE_IS_BUFFER (buffer));
+
+  if (buffer == NULL)
+    {
+      IDE_TRACE_MSG ("Failed to load buffer: %s", error->message);
+      ide_task_return_error (task, g_steal_pointer (&error));
+      IDE_EXIT;
+    }
+
+  if (self->workbench == NULL)
+    IDE_GOTO (failure);
+
+  workspace = ide_workbench_get_current_workspace (self->workbench);
+
+  if (!IDE_IS_PRIMARY_WORKSPACE (workspace) &&
+      !IDE_IS_EDITOR_WORKSPACE (workspace))
+    {
+      workspace = NULL;
+      ide_workbench_foreach_workspace (self->workbench,
+                                       find_preferred_workspace_cb,
+                                       &workspace);
+    }
+
+  if (workspace == NULL)
+    IDE_GOTO (failure);
+
+  state = ide_task_get_task_data (task);
+
+  g_assert (IDE_IS_WORKSPACE (workspace));
+  g_assert (state != NULL);
+  g_assert (G_IS_FILE (state->file));
+
+  if (state->at_line > -1)
+    {
+      g_autoptr(IdeLocation) location = NULL;
+
+      location = ide_location_new (state->file,
+                                   state->at_line,
+                                   state->at_line_offset);
+      ide_editor_focus_location (workspace, state->position, location);
+    }
+  else
+    {
+      ide_editor_focus_buffer (workspace, state->position, buffer);
+    }
+
+failure:
+  ide_task_return_boolean (task, TRUE);
+
+  IDE_EXIT;
+}
+
+static void
+gbp_editorui_workbench_addin_open_async (IdeWorkbenchAddin   *addin,
+                                         GFile               *file,
+                                         const gchar         *content_type,
+                                         gint                 at_line,
+                                         gint                 at_line_offset,
+                                         IdeBufferOpenFlags   flags,
+                                         IdePanelPosition    *position,
+                                         GCancellable        *cancellable,
+                                         GAsyncReadyCallback  callback,
+                                         gpointer             user_data)
+{
+  GbpEditoruiWorkbenchAddin *self = (GbpEditoruiWorkbenchAddin *)addin;
+  IdeBufferManager *buffer_manager;
+  IdeContext *context;
+  OpenFileTaskData *state;
+  g_autoptr(IdeTask) task = NULL;
+
+  g_assert (GBP_IS_EDITORUI_WORKBENCH_ADDIN (self));
+  g_assert (G_IS_FILE (file));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_assert (IDE_IS_WORKBENCH (self->workbench));
+  g_assert (position != NULL);
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  state = g_slice_new0 (OpenFileTaskData);
+  state->flags = flags;
+  state->file = g_object_ref (file);
+  state->at_line = at_line;
+  state->at_line_offset = at_line_offset;
+  state->position = ide_panel_position_ref (position);
+  ide_task_set_task_data (task, state, open_file_task_data_free);
+
+  context = ide_workbench_get_context (self->workbench);
+  buffer_manager = ide_buffer_manager_from_context (context);
+
+  ide_buffer_manager_load_file_async (buffer_manager,
+                                      file,
+                                      state->flags,
+                                      NULL,
+                                      cancellable,
+                                      gbp_editorui_workbench_addin_open_cb,
+                                      g_steal_pointer (&task));
+}
+
+static gboolean
+gbp_editorui_workbench_addin_open_finish (IdeWorkbenchAddin  *addin,
+                                          GAsyncResult       *result,
+                                          GError            **error)
+{
+  g_assert (GBP_IS_EDITORUI_WORKBENCH_ADDIN (addin));
+  g_assert (IDE_IS_TASK (result));
+
+  return ide_task_propagate_boolean (IDE_TASK (result), error);
+}
+
+static void
+workbench_addin_iface_init (IdeWorkbenchAddinInterface *iface)
+{
+  iface->load = gbp_editorui_workbench_addin_load;
+  iface->unload = gbp_editorui_workbench_addin_unload;
+
+  iface->can_open = gbp_editorui_workbench_addin_can_open;
+  iface->open_async = gbp_editorui_workbench_addin_open_async;
+  iface->open_finish = gbp_editorui_workbench_addin_open_finish;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GbpEditoruiWorkbenchAddin, gbp_editorui_workbench_addin, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (IDE_TYPE_WORKBENCH_ADDIN, workbench_addin_iface_init))
+
+static void
+gbp_editorui_workbench_addin_class_init (GbpEditoruiWorkbenchAddinClass *klass)
+{
+  overrides = g_hash_table_new (g_str_hash, g_str_equal);
+  g_hash_table_add (overrides, (char *)".dts"); /* #1572 */
+}
+
+static void
+gbp_editorui_workbench_addin_init (GbpEditoruiWorkbenchAddin *self)
+{
+}
diff --git a/src/plugins/editorui/gbp-editorui-workbench-addin.h 
b/src/plugins/editorui/gbp-editorui-workbench-addin.h
new file mode 100644
index 000000000..ec1ec7809
--- /dev/null
+++ b/src/plugins/editorui/gbp-editorui-workbench-addin.h
@@ -0,0 +1,31 @@
+/* gbp-editorui-workbench-addin.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_EDITORUI_WORKBENCH_ADDIN (gbp_editorui_workbench_addin_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpEditoruiWorkbenchAddin, gbp_editorui_workbench_addin, GBP, 
EDITORUI_WORKBENCH_ADDIN, GObject)
+
+G_END_DECLS
diff --git a/src/plugins/editorui/gbp-editorui-workspace-addin.c 
b/src/plugins/editorui/gbp-editorui-workspace-addin.c
new file mode 100644
index 000000000..44c816dfa
--- /dev/null
+++ b/src/plugins/editorui/gbp-editorui-workspace-addin.c
@@ -0,0 +1,826 @@
+/* gbp-editorui-workspace-addin.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-editorui-workspace-addin"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include <libide-gui.h>
+#include <libide-editor.h>
+
+#include "ide-workspace-private.h"
+
+#include "gbp-editorui-position-label.h"
+#include "gbp-editorui-workspace-addin.h"
+
+struct _GbpEditoruiWorkspaceAddin
+{
+  GObject                   parent_instance;
+
+  IdeWorkspace             *workspace;
+  PanelStatusbar           *statusbar;
+
+  GSimpleActionGroup       *actions;
+
+  IdeBindingGroup          *buffer_bindings;
+  IdeSignalGroup           *buffer_signals;
+  IdeSignalGroup           *view_signals;
+
+  GtkMenuButton            *indentation;
+  GtkLabel                 *indentation_label;
+
+  GtkMenuButton            *line_ends;
+  GtkLabel                 *line_ends_label;
+
+  GtkMenuButton            *position;
+  GbpEditoruiPositionLabel *position_label;
+
+  GtkMenuButton            *encoding;
+  GtkLabel                 *encoding_label;
+
+  GtkLabel                 *syntax_label;
+  GtkMenuButton            *syntax;
+
+  GtkLabel                 *mode_label;
+
+  GSettings                *editor_settings;
+
+  guint                     queued_cursor_moved;
+
+  IdeEditorPage            *page;
+};
+
+#define clear_from_statusbar(s,w) clear_from_statusbar(s, (GtkWidget **)w)
+
+static void
+(clear_from_statusbar) (PanelStatusbar  *statusbar,
+                        GtkWidget      **widget)
+{
+  if (*widget)
+    {
+      panel_statusbar_remove (statusbar, *widget);
+      *widget = NULL;
+    }
+}
+
+static gboolean
+language_to_label (GBinding     *binding,
+                   const GValue *from_value,
+                   GValue       *to_value,
+                   gpointer      user_data)
+{
+  GtkSourceLanguage *language = g_value_get_object (from_value);
+
+  if (language != NULL)
+    g_value_set_string (to_value, gtk_source_language_get_name (language));
+  else
+    /* translators: "Text" means plaintext or text/plain */
+    g_value_set_static_string (to_value, _("Text"));
+
+  return TRUE;
+}
+
+static gboolean
+newline_type_to_label (GBinding     *binding,
+                       const GValue *from_value,
+                       GValue       *to_value,
+                       gpointer      user_data)
+{
+  GtkSourceNewlineType newline_type = g_value_get_enum (from_value);
+
+  switch (newline_type)
+    {
+    default:
+    case GTK_SOURCE_NEWLINE_TYPE_LF:
+      g_value_set_static_string (to_value, "LF");
+      return TRUE;
+
+    case GTK_SOURCE_NEWLINE_TYPE_CR:
+      g_value_set_static_string (to_value, "CR");
+      return TRUE;
+
+    case GTK_SOURCE_NEWLINE_TYPE_CR_LF:
+      g_value_set_static_string (to_value, "CR/LF");
+      return TRUE;
+    }
+}
+
+static void
+notify_overwrite_cb (GbpEditoruiWorkspaceAddin *self)
+{
+  IdeSourceView *view;
+
+  g_assert (GBP_IS_EDITORUI_WORKSPACE_ADDIN (self));
+
+  if ((view = ide_signal_group_get_target (self->view_signals)))
+    {
+      gboolean overwrite = gtk_text_view_get_overwrite (GTK_TEXT_VIEW (view));
+
+      if (overwrite)
+        gtk_label_set_label (self->mode_label, "OVR");
+      else
+        gtk_label_set_label (self->mode_label, "INS");
+    }
+}
+
+static void
+notify_indentation_cb (GbpEditoruiWorkspaceAddin *self)
+{
+  IdeSourceView *view;
+
+  g_assert (GBP_IS_EDITORUI_WORKSPACE_ADDIN (self));
+
+  if ((view = ide_signal_group_get_target (self->view_signals)))
+    {
+      g_autofree char *label = NULL;
+      gboolean insert_spaces_instead_of_tabs;
+      guint tab_width;
+      int indent_width;
+
+      g_object_get (view,
+                    "tab-width", &tab_width,
+                    "indent-width", &indent_width,
+                    "insert-spaces-instead-of-tabs", &insert_spaces_instead_of_tabs,
+                    NULL);
+
+      if (indent_width <= 0)
+        indent_width = tab_width;
+
+      if (indent_width < 0 || indent_width == (int)tab_width)
+        label = g_strdup_printf ("%s: %u",
+                                 insert_spaces_instead_of_tabs ? _("Spaces") : _("Tabs"),
+                                 tab_width);
+      else
+        label = g_strdup_printf ("%s: %u:%u",
+                                 insert_spaces_instead_of_tabs ?  _("Spaces") : _("Tabs"),
+                                 tab_width, indent_width);
+
+      gtk_label_set_label (self->indentation_label, label);
+    }
+}
+
+static void
+update_position (GbpEditoruiWorkspaceAddin *self)
+{
+  IdeSourceView *view;
+
+  g_assert (GBP_IS_EDITORUI_WORKSPACE_ADDIN (self));
+
+  if ((view = ide_signal_group_get_target (self->view_signals)))
+    {
+      guint line, column;
+
+      ide_source_view_get_visual_position (view, &line, &column);
+      gbp_editorui_position_label_update (self->position_label, line, column);
+    }
+}
+
+static gboolean
+update_position_idle (gpointer data)
+{
+  GbpEditoruiWorkspaceAddin *self = data;
+
+  g_assert (GBP_IS_EDITORUI_WORKSPACE_ADDIN (self));
+
+  self->queued_cursor_moved = 0;
+  update_position (self);
+  return G_SOURCE_REMOVE;
+}
+
+static void
+cursor_moved_cb (GbpEditoruiWorkspaceAddin *self)
+{
+  g_assert (GBP_IS_EDITORUI_WORKSPACE_ADDIN (self));
+
+  if (self->queued_cursor_moved)
+    return;
+
+  self->queued_cursor_moved = g_idle_add (update_position_idle, self);
+}
+
+static void
+open_in_new_frame (GSimpleAction *action,
+                   GVariant      *param,
+                   gpointer       user_data)
+{
+  GbpEditoruiWorkspaceAddin *self = user_data;
+
+  IDE_ENTRY;
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+  g_assert (GBP_IS_EDITORUI_WORKSPACE_ADDIN (self));
+
+  IDE_EXIT;
+}
+
+static void
+open_in_new_workspace (GSimpleAction *action,
+                       GVariant      *param,
+                       gpointer       user_data)
+{
+  GbpEditoruiWorkspaceAddin *self = user_data;
+  g_autoptr(IdePanelPosition) position = NULL;
+  IdeEditorWorkspace *workspace;
+  IdeWorkbench *workbench;
+  IdePage *page;
+  IdePage *split;
+
+  IDE_ENTRY;
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+  g_assert (GBP_IS_EDITORUI_WORKSPACE_ADDIN (self));
+
+  if (!(page = ide_workspace_get_most_recent_page (self->workspace)))
+    IDE_EXIT;
+
+  if (!(split = ide_page_create_split (page)))
+    IDE_EXIT;
+
+  workbench = ide_workspace_get_workbench (self->workspace);
+
+  workspace = ide_editor_workspace_new (IDE_APPLICATION_DEFAULT);
+  ide_workbench_add_workspace (workbench, IDE_WORKSPACE (workspace));
+
+  position = ide_panel_position_new ();
+  ide_workspace_add_page (IDE_WORKSPACE (workspace), IDE_PAGE (split), position);
+
+  gtk_window_present (GTK_WINDOW (workspace));
+
+  IDE_EXIT;
+}
+
+static void
+new_workspace (GSimpleAction *action,
+               GVariant      *param,
+               gpointer       user_data)
+{
+  GbpEditoruiWorkspaceAddin *self = user_data;
+  IdeEditorWorkspace *workspace;
+  IdeWorkbench *workbench;
+
+  IDE_ENTRY;
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+  g_assert (GBP_IS_EDITORUI_WORKSPACE_ADDIN (self));
+
+  workbench = ide_workspace_get_workbench (self->workspace);
+  workspace = ide_editor_workspace_new (IDE_APPLICATION_DEFAULT);
+  ide_workbench_add_workspace (workbench, IDE_WORKSPACE (workspace));
+
+  gtk_window_present (GTK_WINDOW (workspace));
+
+  IDE_EXIT;
+}
+
+static void
+new_file_cb (GObject      *object,
+             GAsyncResult *result,
+             gpointer      user_data)
+{
+  IdeBufferManager *bufmgr = (IdeBufferManager *)object;
+  g_autoptr(IdeWorkspace) workspace = user_data;
+  g_autoptr(IdePanelPosition) position = NULL;
+  g_autoptr(IdeBuffer) buffer = NULL;
+  g_autoptr(GError) error = NULL;
+  GtkWidget *page;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_BUFFER_MANAGER (bufmgr));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (!(buffer = ide_buffer_manager_load_file_finish (bufmgr, result, &error)))
+    {
+      g_warning ("Failed to create new buffer: %s", error->message);
+      IDE_EXIT;
+    }
+
+  page = ide_editor_page_new (buffer);
+  position = ide_panel_position_new ();
+  ide_workspace_add_page (workspace, IDE_PAGE (page), position);
+  panel_widget_raise (PANEL_WIDGET (page));
+  gtk_widget_grab_focus (GTK_WIDGET (page));
+
+  IDE_EXIT;
+}
+
+static void
+new_file (GSimpleAction *action,
+          GVariant      *param,
+          gpointer       user_data)
+{
+  GbpEditoruiWorkspaceAddin *self = user_data;
+  IdeBufferManager *bufmgr;
+  IdeContext *context;
+
+  IDE_ENTRY;
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+  g_assert (GBP_IS_EDITORUI_WORKSPACE_ADDIN (self));
+  g_assert (IDE_IS_WORKSPACE (self->workspace));
+
+  context = ide_workspace_get_context (self->workspace);
+  bufmgr = ide_buffer_manager_from_context (context);
+
+  ide_buffer_manager_load_file_async (bufmgr,
+                                      NULL,
+                                      IDE_BUFFER_OPEN_FLAGS_NONE,
+                                      NULL,
+                                      NULL,
+                                      new_file_cb,
+                                      g_object_ref (self->workspace));
+
+  IDE_EXIT;
+}
+
+static void
+go_to_line_activate_cb (GbpEditoruiWorkspaceAddin *self,
+                        const char                *str,
+                        IdeEntryPopover           *entry)
+{
+  int line = -1;
+  int column = -1;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_EDITORUI_WORKSPACE_ADDIN (self));
+  g_assert (IDE_IS_ENTRY_POPOVER (entry));
+
+  if (ide_str_empty0 (str) || sscanf (str, "%d:%d", &line, &column) < 1)
+    IDE_EXIT;
+
+  line--;
+  column--;
+
+  ide_editor_page_scroll_to_visual_position (self->page, MAX (0, line), MAX (0, column));
+  gtk_widget_grab_focus (GTK_WIDGET (self->page));
+
+  IDE_EXIT;
+}
+
+static gboolean
+go_to_line_insert_text_cb (GbpEditoruiWorkspaceAddin *self,
+                           guint                      pos,
+                           const char                *str,
+                           guint                      n_chars,
+                           IdeEntryPopover           *entry)
+{
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_EDITORUI_WORKSPACE_ADDIN (self));
+  g_assert (IDE_IS_ENTRY_POPOVER (entry));
+
+  for (const char *iter = str; *iter; iter = g_utf8_next_char (iter))
+    {
+      if (*iter != ':' && !g_ascii_isdigit (*iter))
+        IDE_RETURN (GDK_EVENT_STOP);
+    }
+
+  IDE_RETURN (GDK_EVENT_PROPAGATE);
+}
+
+static void
+go_to_line_changed_cb (GbpEditoruiWorkspaceAddin *self,
+                       IdeEntryPopover           *entry)
+{
+  const char *text;
+  int line = -1;
+  int column = -1;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_EDITORUI_WORKSPACE_ADDIN (self));
+  g_assert (IDE_IS_ENTRY_POPOVER (entry));
+
+  text = ide_entry_popover_get_text (entry);
+
+  if (ide_str_empty0 (text) ||
+      sscanf (text, "%d:%d", &line, &column) < 1)
+    {
+      ide_entry_popover_set_ready (entry, FALSE);
+      IDE_EXIT;
+    }
+
+  ide_entry_popover_set_ready (entry, TRUE);
+
+  IDE_EXIT;
+}
+
+static void
+show_go_to_line_cb (GbpEditoruiWorkspaceAddin *self,
+                    IdeEntryPopover           *popover)
+{
+  g_autofree char *text = NULL;
+  IdeSourceView *view;
+  guint line;
+  guint column;
+
+  g_assert (GBP_IS_EDITORUI_WORKSPACE_ADDIN (self));
+  g_assert (IDE_IS_ENTRY_POPOVER (popover));
+
+  view = ide_editor_page_get_view (self->page);
+  ide_source_view_get_visual_position (view, &line, &column);
+  text = g_strdup_printf ("%u:%u", line+1, column+1);
+  ide_entry_popover_set_text (popover, text);
+  ide_entry_popover_select_all (popover);
+}
+
+static void
+show_go_to_line (GSimpleAction *action,
+                 GVariant      *param,
+                 gpointer       user_data)
+{
+  GbpEditoruiWorkspaceAddin *self = user_data;
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+
+  if (self->page == NULL)
+    return;
+
+  gtk_menu_button_popup (self->position);
+}
+
+static void
+format_selection_cb (GObject      *object,
+                     GAsyncResult *result,
+                     gpointer      user_data)
+{
+  IdeBuffer *buffer = (IdeBuffer *)object;
+  g_autoptr(IdeSourceView) view = user_data;
+  g_autoptr(GError) error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_BUFFER (buffer));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_SOURCE_VIEW (view));
+
+  if (!ide_buffer_format_selection_finish (buffer, result, &error))
+    {
+      IdeObjectBox *box = ide_object_box_from_object (G_OBJECT (buffer));
+
+      if (!ide_error_ignore (error))
+        /* translators: %s is replaced with the error message */
+        ide_object_warning (box, _("Format Selection Failed: %s"), error->message);
+    }
+
+  gtk_text_view_set_editable (GTK_TEXT_VIEW (view), TRUE);
+
+  IDE_EXIT;
+}
+
+static void
+format_action (GSimpleAction *action,
+               GVariant      *param,
+               gpointer       user_data)
+{
+  IdeSourceView *view = NULL;
+  GbpEditoruiWorkspaceAddin *self = user_data;
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+
+  if ((view = ide_signal_group_get_target (self->view_signals)))
+    {
+      g_autoptr(IdeFormatterOptions) options = NULL;
+      IdeBuffer *buffer;
+      gboolean insert_spaces_instead_of_tabs;
+      guint tab_width;
+
+      g_object_get (view,
+                    "tab-width", &tab_width,
+                    "insert-spaces-instead-of-tabs", &insert_spaces_instead_of_tabs,
+                    NULL);
+
+      options = ide_formatter_options_new ();
+      ide_formatter_options_set_tab_width (options, tab_width);
+      ide_formatter_options_set_insert_spaces (options, insert_spaces_instead_of_tabs);
+
+      /* Disable editing while we format */
+      gtk_text_view_set_editable (GTK_TEXT_VIEW (view), FALSE);
+
+      buffer = ide_signal_group_get_target (self->buffer_signals);
+      ide_buffer_format_selection_async (buffer,
+                                         options,
+                                         NULL,
+                                         format_selection_cb,
+                                         g_object_ref (view));
+    }
+}
+
+static const GActionEntry actions[] = {
+  { "open-in-new-frame", open_in_new_frame },
+  { "open-in-new-workspace", open_in_new_workspace },
+  { "new-file", new_file },
+  { "new-workspace", new_workspace },
+  { "show-go-to-line", show_go_to_line },
+  { "format", format_action },
+};
+
+static void
+gbp_editorui_workspace_addin_load (IdeWorkspaceAddin *addin,
+                                   IdeWorkspace      *workspace)
+{
+  GbpEditoruiWorkspaceAddin *self = (GbpEditoruiWorkspaceAddin *)addin;
+  g_autoptr(GMenuModel) encoding_menu = NULL;
+  g_autoptr(GMenuModel) syntax_menu = NULL;
+  GtkPopover *popover;
+  GMenu *menu;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_EDITORUI_WORKSPACE_ADDIN (self));
+  g_assert (IDE_IS_WORKSPACE (workspace));
+
+  self->workspace = workspace;
+  self->statusbar = ide_workspace_get_statusbar (workspace);
+
+  self->encoding_label = g_object_new (GTK_TYPE_LABEL, NULL);
+  self->line_ends_label = g_object_new (GTK_TYPE_LABEL, NULL);
+  self->syntax_label = g_object_new (GTK_TYPE_LABEL, NULL);
+
+  self->actions = g_simple_action_group_new ();
+  g_action_map_add_action_entries (G_ACTION_MAP (self->actions),
+                                   actions,
+                                   G_N_ELEMENTS (actions),
+                                   self);
+  gtk_widget_insert_action_group (GTK_WIDGET (workspace),
+                                  "editorui",
+                                  G_ACTION_GROUP (self->actions));
+
+  self->buffer_signals = ide_signal_group_new (IDE_TYPE_BUFFER);
+  ide_signal_group_connect_object (self->buffer_signals,
+                                   "cursor-moved",
+                                   G_CALLBACK (cursor_moved_cb),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+
+  self->buffer_bindings = ide_binding_group_new ();
+  ide_binding_group_bind (self->buffer_bindings, "charset",
+                          self->encoding_label, "label",
+                          G_BINDING_SYNC_CREATE);
+  ide_binding_group_bind_full (self->buffer_bindings, "newline-type",
+                               self->line_ends_label, "label",
+                               G_BINDING_SYNC_CREATE,
+                               newline_type_to_label,
+                               NULL, NULL, NULL);
+  ide_binding_group_bind_full (self->buffer_bindings, "language",
+                               self->syntax_label, "label",
+                               G_BINDING_SYNC_CREATE,
+                               language_to_label,
+                               NULL, NULL, NULL);
+
+  self->view_signals = ide_signal_group_new (IDE_TYPE_SOURCE_VIEW);
+  ide_signal_group_connect_object (self->view_signals,
+                                   "notify::indent-width",
+                                   G_CALLBACK (notify_indentation_cb),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+  ide_signal_group_connect_object (self->view_signals,
+                                   "notify::tab-width",
+                                   G_CALLBACK (notify_indentation_cb),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+  ide_signal_group_connect_object (self->view_signals,
+                                   "notify::insert-spaces-instead-of-tabs",
+                                   G_CALLBACK (notify_indentation_cb),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+  ide_signal_group_connect_object (self->view_signals,
+                                   "notify::overwrite",
+                                   G_CALLBACK (notify_overwrite_cb),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+
+  /* Language Syntax */
+  syntax_menu = ide_editor_syntax_menu_new ("editorui.language");
+  self->syntax = g_object_new (GTK_TYPE_MENU_BUTTON,
+                               "menu-model", syntax_menu,
+                               "direction", GTK_ARROW_UP,
+                               "visible", FALSE,
+                               "child", self->syntax_label,
+                               NULL);
+  panel_statusbar_add_suffix (self->statusbar, 1001, GTK_WIDGET (self->syntax));
+
+  /* Line ending */
+  menu = ide_application_get_menu_by_id (IDE_APPLICATION_DEFAULT, "editorui-line-ends-menu");
+  self->line_ends = g_object_new (GTK_TYPE_MENU_BUTTON,
+                                  "menu-model", menu,
+                                  "direction", GTK_ARROW_UP,
+                                  "visible", FALSE,
+                                  "child", self->line_ends_label,
+                                  NULL);
+  panel_statusbar_add_suffix (self->statusbar, 1002, GTK_WIDGET (self->line_ends));
+
+  /* Encoding */
+  encoding_menu = ide_editor_encoding_menu_new ("editorui.encoding");
+  self->encoding = g_object_new (GTK_TYPE_MENU_BUTTON,
+                                 "menu-model", encoding_menu,
+                                 "direction", GTK_ARROW_UP,
+                                 "visible", FALSE,
+                                 "child", self->encoding_label,
+                                 NULL);
+  panel_statusbar_add_suffix (self->statusbar, 1003, GTK_WIDGET (self->encoding));
+
+  /* Indentation status, tabs/spaces/etc */
+  menu = ide_application_get_menu_by_id (IDE_APPLICATION_DEFAULT, "editorui-indent-menu");
+  self->indentation_label = g_object_new (GTK_TYPE_LABEL, NULL);
+  self->indentation = g_object_new (GTK_TYPE_MENU_BUTTON,
+                                    "menu-model", menu,
+                                    "direction", GTK_ARROW_UP,
+                                    "visible", FALSE,
+                                    "child", self->indentation_label,
+                                    NULL);
+  panel_statusbar_add_suffix (self->statusbar, 1004, GTK_WIDGET (self->indentation));
+
+  /* Label for cursor position and jump to line/column */
+  popover = g_object_new (IDE_TYPE_ENTRY_POPOVER,
+                          "button-text", _("Go"),
+                          "title", _("Go to Line"),
+                          NULL);
+  g_signal_connect_object (popover,
+                           "show",
+                           G_CALLBACK (show_go_to_line_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (popover,
+                           "changed",
+                           G_CALLBACK (go_to_line_changed_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (popover,
+                           "insert-text",
+                           G_CALLBACK (go_to_line_insert_text_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (popover,
+                           "activate",
+                           G_CALLBACK (go_to_line_activate_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+  self->position_label = g_object_new (GBP_TYPE_EDITORUI_POSITION_LABEL, NULL);
+  self->position = g_object_new (GTK_TYPE_MENU_BUTTON,
+                                 "direction", GTK_ARROW_UP,
+                                 "visible", FALSE,
+                                 "child", self->position_label,
+                                 "popover", popover,
+                                 NULL);
+  panel_statusbar_add_suffix (self->statusbar, 1005, GTK_WIDGET (self->position));
+
+  self->mode_label = g_object_new (GTK_TYPE_LABEL,
+                                   "label", "INS",
+                                   "width-chars", 4,
+                                   "visible", FALSE,
+                                   NULL);
+  panel_statusbar_add_suffix (self->statusbar, 1006, GTK_WIDGET (self->mode_label));
+
+  self->editor_settings = g_settings_new ("org.gnome.builder.editor");
+
+  IDE_EXIT;
+}
+
+static void
+gbp_editorui_workspace_addin_unload (IdeWorkspaceAddin *addin,
+                                     IdeWorkspace      *workspace)
+{
+  GbpEditoruiWorkspaceAddin *self = (GbpEditoruiWorkspaceAddin *)addin;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_EDITORUI_WORKSPACE_ADDIN (self));
+  g_assert (IDE_IS_WORKSPACE (workspace));
+
+  gtk_widget_insert_action_group (GTK_WIDGET (workspace), "editorui", NULL);
+
+  g_clear_object (&self->buffer_bindings);
+  g_clear_object (&self->buffer_signals);
+  g_clear_object (&self->view_signals);
+  g_clear_object (&self->editor_settings);
+
+  g_clear_handle_id (&self->queued_cursor_moved, g_source_remove);
+
+  clear_from_statusbar (self->statusbar, &self->indentation);
+  clear_from_statusbar (self->statusbar, &self->position);
+
+  self->indentation_label = NULL;
+  self->position_label = NULL;
+
+  self->workspace = NULL;
+  self->statusbar = NULL;
+
+  IDE_EXIT;
+}
+
+static void
+gbp_editorui_workspace_addin_page_changed (IdeWorkspaceAddin *addin,
+                                           IdePage           *page)
+{
+  GbpEditoruiWorkspaceAddin *self = (GbpEditoruiWorkspaceAddin *)addin;
+  g_autofree char *keybindings = NULL;
+  IdeSourceView *view = NULL;
+  IdeBuffer *buffer = NULL;
+
+  g_assert (GBP_IS_EDITORUI_WORKSPACE_ADDIN (self));
+  g_assert (!page || IDE_IS_PAGE (page));
+
+  g_clear_handle_id (&self->queued_cursor_moved, g_source_remove);
+
+  /* Remove now invalid actions */
+  g_action_map_remove_action (G_ACTION_MAP (self->actions), "encoding");
+  g_action_map_remove_action (G_ACTION_MAP (self->actions), "newline-type");
+  g_action_map_remove_action (G_ACTION_MAP (self->actions), "indent-width");
+  g_action_map_remove_action (G_ACTION_MAP (self->actions), "tab-width");
+  g_action_map_remove_action (G_ACTION_MAP (self->actions), "use-spaces");
+  g_action_map_remove_action (G_ACTION_MAP (self->actions), "language");
+
+  if (!IDE_IS_EDITOR_PAGE (page))
+    page = NULL;
+
+  self->page = IDE_EDITOR_PAGE (page);
+
+  if (page != NULL)
+    {
+      g_autoptr(GPropertyAction) encoding_action = NULL;
+      g_autoptr(GPropertyAction) newline_action = NULL;
+      g_autoptr(GPropertyAction) indent_width = NULL;
+      g_autoptr(GPropertyAction) tab_width = NULL;
+      g_autoptr(GPropertyAction) tabs_v_spaces = NULL;
+      g_autoptr(GPropertyAction) language = NULL;
+
+      view = ide_editor_page_get_view (IDE_EDITOR_PAGE (page));
+      buffer = ide_editor_page_get_buffer (IDE_EDITOR_PAGE (page));
+
+      encoding_action = g_property_action_new ("encoding", buffer, "charset");
+      newline_action = g_property_action_new ("newline-type", buffer, "newline-type");
+      indent_width = g_property_action_new ("indent-width", view, "indent-width");
+      tab_width = g_property_action_new ("tab-width", view, "tab-width");
+      tabs_v_spaces = g_property_action_new ("use-spaces", view, "insert-spaces-instead-of-tabs");
+
+      /* TODO: This needs a transform to handle NULL */
+      language = g_property_action_new ("language", buffer, "language-id");
+
+      g_action_map_add_action (G_ACTION_MAP (self->actions), G_ACTION (encoding_action));
+      g_action_map_add_action (G_ACTION_MAP (self->actions), G_ACTION (newline_action));
+      g_action_map_add_action (G_ACTION_MAP (self->actions), G_ACTION (tab_width));
+      g_action_map_add_action (G_ACTION_MAP (self->actions), G_ACTION (indent_width));
+      g_action_map_add_action (G_ACTION_MAP (self->actions), G_ACTION (tabs_v_spaces));
+      g_action_map_add_action (G_ACTION_MAP (self->actions), G_ACTION (language));
+    }
+
+  ide_binding_group_set_source (self->buffer_bindings, buffer);
+  ide_signal_group_set_target (self->buffer_signals, buffer);
+  ide_signal_group_set_target (self->view_signals, view);
+
+  notify_overwrite_cb (self);
+  notify_indentation_cb (self);
+  update_position (self);
+
+  keybindings = g_settings_get_string (self->editor_settings, "keybindings");
+
+  gtk_widget_set_visible (GTK_WIDGET (self->indentation), page != NULL);
+  gtk_widget_set_visible (GTK_WIDGET (self->line_ends), page != NULL);
+  gtk_widget_set_visible (GTK_WIDGET (self->position), page != NULL);
+  gtk_widget_set_visible (GTK_WIDGET (self->encoding), page != NULL);
+  gtk_widget_set_visible (GTK_WIDGET (self->mode_label), page != NULL && !ide_str_equal0 (keybindings, 
"vim"));
+  gtk_widget_set_visible (GTK_WIDGET (self->syntax), page != NULL);
+}
+
+static void
+workspace_addin_iface_init (IdeWorkspaceAddinInterface *iface)
+{
+  iface->load = gbp_editorui_workspace_addin_load;
+  iface->unload = gbp_editorui_workspace_addin_unload;
+  iface->page_changed = gbp_editorui_workspace_addin_page_changed;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GbpEditoruiWorkspaceAddin, gbp_editorui_workspace_addin, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (IDE_TYPE_WORKSPACE_ADDIN, workspace_addin_iface_init))
+
+static void
+gbp_editorui_workspace_addin_class_init (GbpEditoruiWorkspaceAddinClass *klass)
+{
+}
+
+static void
+gbp_editorui_workspace_addin_init (GbpEditoruiWorkspaceAddin *self)
+{
+}
diff --git a/src/plugins/editorui/gbp-editorui-workspace-addin.h 
b/src/plugins/editorui/gbp-editorui-workspace-addin.h
new file mode 100644
index 000000000..9c2d1e551
--- /dev/null
+++ b/src/plugins/editorui/gbp-editorui-workspace-addin.h
@@ -0,0 +1,31 @@
+/* gbp-editorui-workspace-addin.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_EDITORUI_WORKSPACE_ADDIN (gbp_editorui_workspace_addin_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpEditoruiWorkspaceAddin, gbp_editorui_workspace_addin, GBP, 
EDITORUI_WORKSPACE_ADDIN, GObject)
+
+G_END_DECLS
diff --git a/src/plugins/editorui/gtk/keybindings.json b/src/plugins/editorui/gtk/keybindings.json
new file mode 100644
index 000000000..3003dfeaa
--- /dev/null
+++ b/src/plugins/editorui/gtk/keybindings.json
@@ -0,0 +1,2 @@
+{ "trigger" : "<Control>i", "action" : "editorui.show-go-to-line", "when" : "canEdit()", "phase" : "capture" 
},
+{ "trigger" : "<Shift><Alt>f", "action": "editorui.format", "when" : "canEdit()", "phase" : "capture" },
diff --git a/src/plugins/editorui/gtk/menus.ui b/src/plugins/editorui/gtk/menus.ui
new file mode 100644
index 000000000..8b3d9f7f0
--- /dev/null
+++ b/src/plugins/editorui/gtk/menus.ui
@@ -0,0 +1,157 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <menu id="editorui-indent-menu">
+    <section id="editorui-indent-section">
+      <attribute name="label" translatable="yes">Indentation</attribute>
+      <item>
+        <attribute name="label" translatable="yes">Indent Using Spaces</attribute>
+        <attribute name="action">editorui.use-spaces</attribute>
+      </item>
+    </section>
+    <section id="editorui-tab-section">
+      <submenu id="editorui-tab-submenu">
+        <attribute name="label" translatable="yes">Spaces per Tab</attribute>
+        <item>
+          <attribute name="label" translatable="yes">2</attribute>
+          <attribute name="action">editorui.tab-width</attribute>
+          <attribute name="target" type="u">2</attribute>
+        </item>
+        <item>
+          <attribute name="label" translatable="yes">3</attribute>
+          <attribute name="action">editorui.tab-width</attribute>
+          <attribute name="target" type="u">3</attribute>
+        </item>
+        <item>
+          <attribute name="label" translatable="yes">4</attribute>
+          <attribute name="action">editorui.tab-width</attribute>
+          <attribute name="target" type="u">4</attribute>
+        </item>
+        <item>
+          <attribute name="label" translatable="yes">5</attribute>
+          <attribute name="action">editorui.tab-width</attribute>
+          <attribute name="target" type="u">5</attribute>
+        </item>
+        <item>
+          <attribute name="label" translatable="yes">8</attribute>
+          <attribute name="action">editorui.tab-width</attribute>
+          <attribute name="target" type="u">8</attribute>
+        </item>
+      </submenu>
+      <submenu id="editorui-indent-submenu">
+        <attribute name="label" translatable="yes">Indentation Size</attribute>
+        <section id="indent-inherit-size-section">
+          <item>
+            <attribute name="label" translatable="yes">Same as Tab Width</attribute>
+            <attribute name="action">editorui.indent-width</attribute>
+            <attribute name="target" type="i">-1</attribute>
+          </item>
+        </section>
+        <section id="indent-common-sizes-section">
+          <item>
+            <attribute name="label" translatable="yes">2</attribute>
+            <attribute name="action">editorui.indent-width</attribute>
+            <attribute name="target" type="i">2</attribute>
+          </item>
+          <item>
+            <attribute name="label" translatable="yes">3</attribute>
+            <attribute name="action">editorui.indent-width</attribute>
+            <attribute name="target" type="i">3</attribute>
+          </item>
+          <item>
+            <attribute name="label" translatable="yes">4</attribute>
+            <attribute name="action">editorui.indent-width</attribute>
+            <attribute name="target" type="i">4</attribute>
+          </item>
+          <item>
+            <attribute name="label" translatable="yes">5</attribute>
+            <attribute name="action">editorui.indent-width</attribute>
+            <attribute name="target" type="i">5</attribute>
+          </item>
+          <item>
+            <attribute name="label" translatable="yes">8</attribute>
+            <attribute name="action">editorui.indent-width</attribute>
+            <attribute name="target" type="i">8</attribute>
+          </item>
+        </section>
+      </submenu>
+    </section>
+  </menu>
+  <menu id="editorui-line-ends-menu">
+    <section id="editorui-line-ends-section">
+      <attribute name="label">Line Ending</attribute>
+      <!--item>
+        This item is inserted automatically when the application starts
+        so that we can alter what label is shown.
+        <attribute name="label">Linux (LF)</attribute>
+        <attribute name="action">editorui.newline-type</attribute>
+        <attribute name="target" type="s">'lf'</attribute>
+      </item-->
+      <item>
+        <attribute name="label">Windows (CR/LF)</attribute>
+        <attribute name="action">editorui.newline-type</attribute>
+        <attribute name="target" type="s">'cr-lf'</attribute>
+      </item>
+      <item>
+        <attribute name="label">Mac Classic (CR)</attribute>
+        <attribute name="action">editorui.newline-type</attribute>
+        <attribute name="target" type="s">'cr'</attribute>
+      </item>
+    </section>
+  </menu>
+  <menu id="ide-editor-page-menu">
+    <section id="ide-editor-page-document-section">
+      <attribute name="label" translatable="yes">Document</attribute>
+      <item>
+        <attribute name="label" translatable="yes">Open in New Frame</attribute>
+        <attribute name="id">editor-document-open-in-new-workspace</attribute>
+        <attribute name="action">editorui.open-in-new-frame</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">Open in New Workspace…</attribute>
+        <attribute name="id">editor-document-open-in-new-workspace</attribute>
+        <attribute name="action">editorui.open-in-new-workspace</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">Print…</attribute>
+      </item>
+    </section>
+  </menu>
+  <menu id="ide-editor-workspace-menu">
+    <section id="ide-editor-workspace-menu-placeholder1">
+      <item>
+        <attribute name="label" translatable="yes">New Editor Workspace…</attribute>
+        <attribute name="id">ide-editor-workspace-new-workspace</attribute>
+        <attribute name="action">editorui.new-workspace</attribute>
+      </item>
+    </section>
+  </menu>
+  <menu id="ide-primary-workspace-menu">
+    <section id="ide-primary-workspace-menu-placeholder1">
+      <item>
+        <attribute name="label" translatable="yes">New Editor Workspace…</attribute>
+        <attribute name="id">ide-editor-workspace-new-workspace</attribute>
+        <attribute name="action">editorui.new-workspace</attribute>
+      </item>
+    </section>
+  </menu>
+  <menu id="new-document-menu">
+    <section id="new-document-section">
+      <attribute name="id">new-document-section</attribute>
+      <item>
+        <attribute name="id">new-file</attribute>
+        <attribute name="label" translatable="yes">New _File</attribute>
+        <attribute name="action">editorui.new-file</attribute>
+        <attribute name="accel">&lt;primary&gt;n</attribute>
+      </item>
+    </section>
+    <section id="open-document-section">
+      <attribute name="id">open-document-section</attribute>
+      <item>
+        <attribute name="id">open-file</attribute>
+        <attribute name="label" translatable="yes">_Open File…</attribute>
+        <attribute name="action">workbench.open</attribute>
+        <attribute name="accel">&lt;primary&gt;o</attribute>
+      </item>
+    </section>
+  </menu>
+</interface>
diff --git a/src/plugins/editorui/meson.build b/src/plugins/editorui/meson.build
new file mode 100644
index 000000000..94ce0eee2
--- /dev/null
+++ b/src/plugins/editorui/meson.build
@@ -0,0 +1,17 @@
+plugins_sources += files([
+  'editorui-plugin.c',
+  'gbp-editorui-application-addin.c',
+  'gbp-editorui-position-label.c',
+  'gbp-editorui-preferences-addin.c',
+  'gbp-editorui-preview.c',
+  'gbp-editorui-workbench-addin.c',
+  'gbp-editorui-workspace-addin.c',
+])
+
+plugin_editorui_resources = gnome.compile_resources(
+  'gbp-editorui-resources',
+  'editorui.gresource.xml',
+  c_name: 'gbp_editorui',
+)
+
+plugins_sources += plugin_editorui_resources
diff --git a/src/plugins/editorui/style.css b/src/plugins/editorui/style.css
new file mode 100644
index 000000000..2bce7c332
--- /dev/null
+++ b/src/plugins/editorui/style.css
@@ -0,0 +1,14 @@
+window.preferences preferencesgroup flowbox.style-schemes flowboxchild {
+  outline-offset: 2px;
+  border-radius: 12px;
+  outline-width: 2px;
+  padding: 0;
+}
+window.preferences preferencesgroup flowbox.style-schemes flowboxchild GtkSourceStyleSchemePreview {
+  margin: 0;
+}
+window.preferences preferencesgroup flowbox.style-schemes flowboxchild 
GtkSourceStyleSchemePreview:not(.selected) {
+  box-shadow: 0 0 0 1px alpha(black, 0.03),
+              0 1px 3px 1px alpha(black, .07),
+              0 2px 6px 2px alpha(black, .03);
+}


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