[gnome-text-editor] vim: add support for vim keybindings



commit 010861934cbb278b92274099f7b1c2d9524d13f1
Author: Christian Hergert <chergert redhat com>
Date:   Thu Nov 11 23:21:45 2021 -0800

    vim: add support for vim keybindings
    
    This is currently hidden behind the gsettings 'keybindings' key. You may
    `gsettings set org.gnome.TextEditor keybindings vim` to enable it, although
    in Flatpak you'll need to edit the settings file.
    
    This uses the new GtkSourceVimIMContext along with a command bar widget on
    the bottom that is only visible when Vim bindings are enabled.
    
    Use with caution, and file bugs please (or better yet, fix them).

 data/org.gnome.TextEditor.gschema.xml |   9 ++
 src/TextEditor.css                    |   8 ++
 src/editor-page-private.h             |   3 +
 src/editor-page-vim.c                 | 208 ++++++++++++++++++++++++++++++++++
 src/editor-page.c                     |   4 +
 src/editor-page.ui                    |  11 ++
 src/meson.build                       |   1 +
 7 files changed, 244 insertions(+)
---
diff --git a/data/org.gnome.TextEditor.gschema.xml b/data/org.gnome.TextEditor.gschema.xml
index fc4bb2c..d95c028 100644
--- a/data/org.gnome.TextEditor.gschema.xml
+++ b/data/org.gnome.TextEditor.gschema.xml
@@ -108,5 +108,14 @@
       <summary>Restore session</summary>
       <description>When Text Editor is run, restore the previous session.</description>
     </key>
+    <key name="keybindings" type="s">
+      <choices>
+        <choice value="default"/>
+        <choice value="vim"/>
+      </choices>
+      <default>'default'</default>
+      <summary>Keybindings</summary>
+      <description>The keybindings to use within Text Editor.</description>
+    </key>
   </schema>
 </schemalist>
diff --git a/src/TextEditor.css b/src/TextEditor.css
index e5476c0..d4f1ff8 100644
--- a/src/TextEditor.css
+++ b/src/TextEditor.css
@@ -117,3 +117,11 @@ textview.source-map:dir(rtl) {
 flap > scrolledwindow.preferences > viewport {
   background: @theme_bg_color;
 }
+
+label.vim-command-bar {
+  font-family: monospace;
+  min-width: 100px;
+  padding: 3px 6px;
+  background: @theme_bg_color;
+  border-top: 1px solid @borders;
+}
diff --git a/src/editor-page-private.h b/src/editor-page-private.h
index b36d843..ea6dea9 100644
--- a/src/editor-page-private.h
+++ b/src/editor-page-private.h
@@ -57,6 +57,8 @@ struct _EditorPage
   EditorSearchBar         *search_bar;
   GtkInfoBar              *changed_infobar;
   GtkInfoBar              *infobar;
+  GtkLabel                *vim_command_bar;
+  GtkEventController      *vim;
 
   guint                    close_requested : 1;
   guint                    moving : 1;
@@ -93,5 +95,6 @@ void          _editor_page_move_previous_search   (EditorPage           *self,
                                                    gboolean              hide_after_search);
 void          _editor_page_begin_move             (EditorPage           *self);
 void          _editor_page_end_move               (EditorPage           *self);
+void          _editor_page_vim_init               (EditorPage           *self);
 
 G_END_DECLS
diff --git a/src/editor-page-vim.c b/src/editor-page-vim.c
new file mode 100644
index 0000000..87afa2b
--- /dev/null
+++ b/src/editor-page-vim.c
@@ -0,0 +1,208 @@
+/* editor-page-vim.c
+ *
+ * Copyright 2021 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
+ */
+
+#include "config.h"
+
+#include "editor-application-private.h"
+#include "editor-page-private.h"
+#include "editor-session.h"
+
+static void
+on_notify_command_bar_text_cb (EditorPage            *self,
+                               GParamSpec            *pspec,
+                               GtkSourceVimIMContext *im_context)
+{
+  const char *label;
+
+  g_assert (EDITOR_IS_PAGE (self));
+  g_assert (GTK_SOURCE_IS_VIM_IM_CONTEXT (im_context));
+
+  label = gtk_source_vim_im_context_get_command_bar_text (im_context);
+  gtk_label_set_label (self->vim_command_bar, label);
+}
+
+static void
+on_vim_write_cb (EditorPage            *self,
+                 GtkSourceView         *view,
+                 const char            *path,
+                 GtkSourceVimIMContext *im_context)
+{
+  g_assert (EDITOR_IS_PAGE (self));
+  g_assert (GTK_SOURCE_IS_VIEW (view));
+  g_assert (GTK_SOURCE_IS_VIM_IM_CONTEXT (im_context));
+
+  if (path != NULL)
+    _editor_page_save_as (self, path);
+  else
+    _editor_page_save (self);
+}
+
+static void
+on_vim_edit_cb (EditorPage            *self,
+                GtkSourceView         *view,
+                const char            *path,
+                GtkSourceVimIMContext *im_context)
+{
+  g_assert (EDITOR_IS_PAGE (self));
+  g_assert (GTK_SOURCE_IS_VIEW (view));
+  g_assert (GTK_SOURCE_IS_VIM_IM_CONTEXT (im_context));
+
+  if (path != NULL)
+    {
+      GFile *file = editor_document_get_file (self->document);
+      g_autoptr(GFile) selected = NULL;
+
+      if (file != NULL && !g_path_is_absolute (path))
+        {
+          g_autoptr(GFile) parent = g_file_get_parent (file);
+          selected = g_file_get_child (parent, path);
+        }
+      else
+        {
+          selected = g_file_new_for_path (path);
+        }
+
+      editor_session_open (EDITOR_SESSION_DEFAULT,
+                           EDITOR_WINDOW (gtk_widget_get_native (GTK_WIDGET (self))),
+                           selected,
+                           NULL);
+    }
+  else
+    {
+      _editor_page_discard_changes (self);
+    }
+}
+
+static void
+close_after_complete_cb (GObject      *object,
+                         GAsyncResult *result,
+                         gpointer      user_data)
+{
+  g_autoptr(EditorPage) self = user_data;
+
+  g_assert (EDITOR_IS_PAGE (self));
+
+  editor_session_remove_page (EDITOR_SESSION_DEFAULT, self);
+}
+
+static gboolean
+on_vim_execute_command_cb (EditorPage            *self,
+                           const char            *command,
+                           GtkSourceVimIMContext *im_context)
+{
+  g_assert (EDITOR_IS_PAGE (self));
+  g_assert (GTK_SOURCE_IS_VIM_IM_CONTEXT (im_context));
+
+  if (g_str_equal (command, ":q") || g_str_equal (command, ":quit"))
+    {
+      editor_session_remove_page (EDITOR_SESSION_DEFAULT, self);
+      return TRUE;
+    }
+
+  if (g_str_equal (command, ":q!") || g_str_equal (command, ":quit!"))
+    {
+      _editor_page_discard_changes_async (self, FALSE, NULL, close_after_complete_cb, g_object_ref (self));
+      return TRUE;
+    }
+
+  if (g_str_equal (command, ":wq"))
+    {
+      _editor_document_save_async (self->document, NULL, NULL, close_after_complete_cb, g_object_ref (self));
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+on_keybindings_changed_cb (EditorPage *self,
+                           const char *key,
+                           GSettings  *settings)
+{
+  g_autofree char *choice = NULL;
+
+  g_assert (EDITOR_IS_PAGE (self));
+  g_assert (G_IS_SETTINGS (settings));
+
+  choice = g_settings_get_string (settings, "keybindings");
+
+  if (g_str_equal (choice, "vim"))
+    {
+      if (self->vim == NULL)
+        {
+          GtkIMContext *im_context;
+
+          im_context = gtk_source_vim_im_context_new ();
+          g_signal_connect_object (im_context,
+                                   "notify::command-bar-text",
+                                   G_CALLBACK (on_notify_command_bar_text_cb),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+          g_signal_connect_object (im_context,
+                                   "write",
+                                   G_CALLBACK (on_vim_write_cb),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+          g_signal_connect_object (im_context,
+                                   "edit",
+                                   G_CALLBACK (on_vim_edit_cb),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+          g_signal_connect_object (im_context,
+                                   "execute-command",
+                                   G_CALLBACK (on_vim_execute_command_cb),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+          gtk_im_context_set_client_widget (im_context, GTK_WIDGET (self->view));
+
+          self->vim = gtk_event_controller_key_new ();
+          gtk_event_controller_set_propagation_phase (self->vim, GTK_PHASE_CAPTURE);
+          gtk_event_controller_key_set_im_context (GTK_EVENT_CONTROLLER_KEY (self->vim), im_context);
+          gtk_widget_add_controller (GTK_WIDGET (self->view), self->vim);
+
+          gtk_widget_show (GTK_WIDGET (self->vim_command_bar));
+        }
+    }
+  else
+    {
+      if (self->vim)
+        {
+          gtk_label_set_label (self->vim_command_bar, NULL);
+          gtk_widget_hide (GTK_WIDGET (self->vim_command_bar));
+          gtk_widget_remove_controller (GTK_WIDGET (self), self->vim);
+          self->vim = NULL;
+        }
+    }
+}
+
+void
+_editor_page_vim_init (EditorPage *self)
+{
+  EditorApplication *app = EDITOR_APPLICATION_DEFAULT;
+
+  g_return_if_fail (EDITOR_IS_PAGE (self));
+
+  g_signal_connect_object (app->settings,
+                           "changed::keybindings",
+                           G_CALLBACK (on_keybindings_changed_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+  on_keybindings_changed_cb (self, NULL, app->settings);
+}
diff --git a/src/editor-page.c b/src/editor-page.c
index 5605101..118701b 100644
--- a/src/editor-page.c
+++ b/src/editor-page.c
@@ -25,6 +25,7 @@
 #include <adwaita.h>
 #include <glib/gi18n.h>
 
+#include "editor-application-private.h"
 #include "editor-info-bar-private.h"
 #include "editor-page-private.h"
 #include "editor-sidebar-model-private.h"
@@ -341,6 +342,8 @@ editor_page_constructed (GObject *object)
 
   editor_page_document_notify_busy_cb (self, NULL, self->document);
   editor_page_document_notify_language_cb (self, NULL, self->document);
+
+  _editor_page_vim_init (self);
 }
 
 static gboolean
@@ -627,6 +630,7 @@ editor_page_class_init (EditorPageClass *klass)
   gtk_widget_class_bind_template_child (widget_class, EditorPage, search_bar);
   gtk_widget_class_bind_template_child (widget_class, EditorPage, search_revealer);
   gtk_widget_class_bind_template_child (widget_class, EditorPage, view);
+  gtk_widget_class_bind_template_child (widget_class, EditorPage, vim_command_bar);
   gtk_widget_class_bind_template_callback (widget_class, get_child_position_cb);
   gtk_widget_class_bind_template_callback (widget_class, goto_line_entry_activate_cb);
 
diff --git a/src/editor-page.ui b/src/editor-page.ui
index 3deca11..97c0c14 100644
--- a/src/editor-page.ui
+++ b/src/editor-page.ui
@@ -145,6 +145,17 @@
             </child>
           </object>
         </child>
+        <child>
+          <object class="GtkLabel" id="vim_command_bar">
+            <property name="xalign">0</property>
+            <property name="valign">end</property>
+            <property name="vexpand">false</property>
+            <property name="visible">false</property>
+            <style>
+              <class name="vim-command-bar"/>
+            </style>
+          </object>
+        </child>
       </object>
     </child>
   </template>
diff --git a/src/meson.build b/src/meson.build
index 4769521..1f8e497 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -17,6 +17,7 @@ editor_sources = [
   'editor-page-gsettings.c',
   'editor-page-settings.c',
   'editor-page-settings-provider.c',
+  'editor-page-vim.c',
   'editor-path.c',
   'editor-position-label.c',
   'editor-preferences-font.c',


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