[gnome-builder/wip/vim] vim: start on vim keybindings prototype.



commit 77673c3f06d6492740b22d2b1a92ccbaa67df51c
Author: Christian Hergert <christian hergert me>
Date:   Mon Sep 29 15:22:20 2014 -0700

    vim: start on vim keybindings prototype.

 src/editor/gb-editor-tab-private.h |    6 +
 src/editor/gb-editor-tab.c         |    5 +
 src/editor/gb-editor-vim.c         |  884 ++++++++++++++++++++++++++++++++++++
 src/editor/gb-editor-vim.h         |   70 +++
 src/editor/gb-source-view.h        |   16 +-
 src/gnome-builder.mk               |    2 +
 6 files changed, 974 insertions(+), 9 deletions(-)
---
diff --git a/src/editor/gb-editor-tab-private.h b/src/editor/gb-editor-tab-private.h
index 0122ad2..eb352ba 100644
--- a/src/editor/gb-editor-tab-private.h
+++ b/src/editor/gb-editor-tab-private.h
@@ -26,6 +26,7 @@
 #include "gb-box-theatric.h"
 #include "gb-editor-document.h"
 #include "gb-editor-settings.h"
+#include "gb-editor-vim.h"
 #include "gb-markdown-preview.h"
 #include "gb-notebook.h"
 #include "gb-source-auto-indenter.h"
@@ -70,6 +71,11 @@ struct _GbEditorTabPrivate
   GbEditorSettings *settings;
 
   /*
+   * VIM mode helper.
+   */
+  GbEditorVim *vim;
+
+  /*
    * Weak reference bindings for tracking settings.
    */
   GBinding *auto_indent_binding;
diff --git a/src/editor/gb-editor-tab.c b/src/editor/gb-editor-tab.c
index 0d00e04..84f3213 100644
--- a/src/editor/gb-editor-tab.c
+++ b/src/editor/gb-editor-tab.c
@@ -1081,6 +1081,11 @@ gb_editor_tab_constructed (GObject *object)
                     NULL);
   gtk_source_gutter_insert (gutter, priv->change_renderer, 0);
 
+  priv->vim = g_object_new (GB_TYPE_EDITOR_VIM,
+                            "enabled", TRUE,
+                            "text-view", priv->source_view,
+                            NULL);
+
   gb_editor_tab_cursor_moved (tab, priv->document);
 
   EXIT;
diff --git a/src/editor/gb-editor-vim.c b/src/editor/gb-editor-vim.c
new file mode 100644
index 0000000..35ecd7d
--- /dev/null
+++ b/src/editor/gb-editor-vim.c
@@ -0,0 +1,884 @@
+/* gb-editor-vim.c
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "vim"
+
+#include <glib/gi18n.h>
+#include <gtksourceview/gtksource.h>
+
+#include "gb-editor-vim.h"
+#include "gb-log.h"
+
+struct _GbEditorVimPrivate
+{
+  GtkTextView     *text_view;
+  GbEditorVimMode  mode;
+  guint            key_press_event_handler;
+  guint            target_line_offset;
+  guint            enabled : 1;
+};
+
+enum
+{
+  PROP_0,
+  PROP_ENABLED,
+  PROP_MODE,
+  PROP_TEXT_VIEW,
+  LAST_PROP
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GbEditorVim, gb_editor_vim, G_TYPE_OBJECT)
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+
+GbEditorVim *
+gb_editor_vim_new (GtkTextView *text_view)
+{
+  g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), NULL);
+
+  return g_object_new (GB_TYPE_EDITOR_VIM,
+                       "text-view", text_view,
+                       NULL);
+}
+
+GbEditorVimMode
+gb_editor_vim_get_mode (GbEditorVim *vim)
+{
+  g_return_val_if_fail (GB_IS_EDITOR_VIM (vim), 0);
+
+  return vim->priv->mode;
+}
+
+static guint
+gb_editor_vim_get_line_offset (GbEditorVim *vim)
+{
+  GtkTextBuffer *buffer;
+  GtkTextMark *insert;
+  GtkTextIter iter;
+
+  buffer = gtk_text_view_get_buffer (vim->priv->text_view);
+  insert = gtk_text_buffer_get_insert (buffer);
+  gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
+
+  return gtk_text_iter_get_line_offset (&iter);
+}
+
+static void
+gb_editor_vim_set_mode (GbEditorVim     *vim,
+                        GbEditorVimMode  mode)
+{
+  g_return_if_fail (GB_IS_EDITOR_VIM (vim));
+
+  vim->priv->mode = mode;
+
+  if (mode == GB_EDITOR_VIM_NORMAL)
+    vim->priv->target_line_offset = gb_editor_vim_get_line_offset (vim);
+
+  g_object_notify_by_pspec (G_OBJECT (vim), gParamSpecs [PROP_MODE]);
+}
+
+static void
+gb_editor_vim_move_line_start (GbEditorVim *vim)
+{
+  GbEditorVimPrivate *priv;
+  GtkTextBuffer *buffer;
+  GtkTextMark *insert;
+  GtkTextIter iter;
+
+  g_assert (GB_IS_EDITOR_VIM (vim));
+
+  priv = vim->priv;
+
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
+  insert = gtk_text_buffer_get_insert (buffer);
+  gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
+
+  gtk_text_buffer_get_iter_at_line (buffer, &iter,
+                                    gtk_text_iter_get_line (&iter));
+
+  while (!gtk_text_iter_ends_line (&iter) &&
+         g_unichar_isspace (gtk_text_iter_get_char (&iter)))
+    if (!gtk_text_iter_forward_char (&iter))
+      break;
+
+  gtk_text_buffer_select_range (buffer, &iter, &iter);
+
+  vim->priv->target_line_offset = gb_editor_vim_get_line_offset (vim);
+}
+
+static void
+gb_editor_vim_move_line_end (GbEditorVim *vim)
+{
+  GbEditorVimPrivate *priv;
+  GtkTextBuffer *buffer;
+  GtkTextMark *insert;
+  GtkTextIter iter;
+
+  g_assert (GB_IS_EDITOR_VIM (vim));
+
+  priv = vim->priv;
+
+  buffer = gtk_text_view_get_buffer (priv->text_view);
+  insert = gtk_text_buffer_get_insert (buffer);
+  gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
+
+  while (!gtk_text_iter_ends_line (&iter))
+    if (!gtk_text_iter_forward_char (&iter))
+      break;
+
+  gtk_text_buffer_select_range (buffer, &iter, &iter);
+
+  vim->priv->target_line_offset = gb_editor_vim_get_line_offset (vim);
+}
+
+static void
+gb_editor_vim_move_backward (GbEditorVim *vim)
+{
+  GbEditorVimPrivate *priv;
+  GtkTextBuffer *buffer;
+  GtkTextMark *insert;
+  GtkTextIter iter;
+  guint line;
+
+  g_assert (GB_IS_EDITOR_VIM (vim));
+
+  priv = vim->priv;
+
+  buffer = gtk_text_view_get_buffer (priv->text_view);
+  insert = gtk_text_buffer_get_insert (buffer);
+  gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
+  line = gtk_text_iter_get_line (&iter);
+
+  /*
+   * TODO: handle there being a selection.
+   */
+
+  if (gtk_text_iter_backward_char (&iter) &&
+      (line == gtk_text_iter_get_line (&iter)))
+    gtk_text_buffer_select_range (buffer, &iter, &iter);
+
+  vim->priv->target_line_offset = gb_editor_vim_get_line_offset (vim);
+}
+
+static void
+gb_editor_vim_move_backward_word (GbEditorVim *vim)
+{
+  GtkTextBuffer *buffer;
+  GtkTextIter iter;
+  GtkTextMark *insert;
+
+  g_assert (GB_IS_EDITOR_VIM (vim));
+
+  buffer = gtk_text_view_get_buffer (vim->priv->text_view);
+  insert = gtk_text_buffer_get_insert (buffer);
+  gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
+
+  if (!gtk_text_iter_backward_word_start (&iter))
+    gtk_text_buffer_get_start_iter (buffer, &iter);
+
+  gtk_text_buffer_select_range (buffer, &iter, &iter);
+
+  vim->priv->target_line_offset = gb_editor_vim_get_line_offset (vim);
+
+  gtk_text_view_scroll_mark_onscreen (vim->priv->text_view, insert);
+}
+
+static void
+gb_editor_vim_move_forward (GbEditorVim *vim)
+{
+  GbEditorVimPrivate *priv;
+  GtkTextBuffer *buffer;
+  GtkTextMark *insert;
+  GtkTextIter iter;
+  guint line;
+
+  g_assert (GB_IS_EDITOR_VIM (vim));
+
+  priv = vim->priv;
+
+  buffer = gtk_text_view_get_buffer (priv->text_view);
+  insert = gtk_text_buffer_get_insert (buffer);
+  gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
+  line = gtk_text_iter_get_line (&iter);
+
+  /*
+   * TODO: handle there being a selection.
+   */
+
+  if (gtk_text_iter_forward_char (&iter) &&
+      (line == gtk_text_iter_get_line (&iter)))
+    gtk_text_buffer_select_range (buffer, &iter, &iter);
+
+  vim->priv->target_line_offset = gb_editor_vim_get_line_offset (vim);
+}
+
+static void
+gb_editor_vim_move_forward_word (GbEditorVim *vim)
+{
+  GtkTextBuffer *buffer;
+  GtkTextIter iter;
+  GtkTextMark *insert;
+
+  g_assert (GB_IS_EDITOR_VIM (vim));
+
+  buffer = gtk_text_view_get_buffer (vim->priv->text_view);
+  insert = gtk_text_buffer_get_insert (buffer);
+  gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
+
+  /*
+   * TODO: handle there being a selection.
+   */
+
+  if (!g_unichar_isspace (gtk_text_iter_get_char (&iter)) &&
+      !gtk_text_iter_ends_word (&iter))
+    if (!gtk_text_iter_forward_word_end (&iter))
+      return;
+
+  if (!gtk_text_iter_forward_word_end (&iter) ||
+      !gtk_text_iter_backward_word_start (&iter))
+    gtk_text_buffer_get_end_iter (buffer, &iter);
+
+  gtk_text_buffer_select_range (buffer, &iter, &iter);
+
+  vim->priv->target_line_offset = gb_editor_vim_get_line_offset (vim);
+
+  gtk_text_view_scroll_mark_onscreen (vim->priv->text_view, insert);
+}
+
+static void
+gb_editor_vim_move_down (GbEditorVim *vim)
+{
+  GbEditorVimPrivate *priv;
+  GtkTextBuffer *buffer;
+  GtkTextMark *insert;
+  GtkTextIter iter;
+  guint line;
+  guint offset;
+
+  g_assert (GB_IS_EDITOR_VIM (vim));
+
+  priv = vim->priv;
+
+  buffer = gtk_text_view_get_buffer (priv->text_view);
+  insert = gtk_text_buffer_get_insert (buffer);
+  gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
+  line = gtk_text_iter_get_line (&iter);
+  offset = vim->priv->target_line_offset;
+
+  /*
+   * TODO: handle there being a selection.
+   */
+
+  gtk_text_buffer_get_iter_at_line (buffer, &iter, line + 1);
+  if ((line + 1) == gtk_text_iter_get_line (&iter))
+    {
+      for (; offset; offset--)
+        if (!gtk_text_iter_ends_line (&iter))
+          if (!gtk_text_iter_forward_char (&iter))
+            break;
+      gtk_text_buffer_select_range (buffer, &iter, &iter);
+    }
+
+  gtk_text_view_scroll_mark_onscreen (vim->priv->text_view, insert);
+}
+
+static void
+gb_editor_vim_move_up (GbEditorVim *vim)
+{
+  GbEditorVimPrivate *priv;
+  GtkTextBuffer *buffer;
+  GtkTextMark *insert;
+  GtkTextIter iter;
+  guint line;
+  guint offset;
+
+  g_assert (GB_IS_EDITOR_VIM (vim));
+
+  priv = vim->priv;
+
+  buffer = gtk_text_view_get_buffer (priv->text_view);
+  insert = gtk_text_buffer_get_insert (buffer);
+  gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
+  line = gtk_text_iter_get_line (&iter);
+  offset = vim->priv->target_line_offset;
+
+  if (line == 0)
+    return;
+
+  /*
+   * TODO: handle there being a selection.
+   */
+
+  gtk_text_buffer_get_iter_at_line (buffer, &iter, line - 1);
+  if ((line - 1) == gtk_text_iter_get_line (&iter))
+    {
+      for (; offset; offset--)
+        if (!gtk_text_iter_ends_line (&iter))
+          if (!gtk_text_iter_forward_char (&iter))
+            break;
+      gtk_text_buffer_select_range (buffer, &iter, &iter);
+    }
+
+  gtk_text_view_scroll_mark_onscreen (vim->priv->text_view, insert);
+}
+
+static void
+gb_editor_vim_delete_selection (GbEditorVim *vim)
+{
+  GtkTextBuffer *buffer;
+  GtkTextMark *insert;
+  GtkTextIter begin;
+  GtkTextIter end;
+
+  g_assert (GB_IS_EDITOR_VIM (vim));
+
+  buffer = gtk_text_view_get_buffer (vim->priv->text_view);
+  gtk_text_buffer_get_selection_bounds (buffer, &begin, &end);
+
+  if (gtk_text_iter_equal (&begin, &end))
+    if (!gtk_text_iter_forward_char (&end))
+      return;
+
+  gtk_text_buffer_begin_user_action (buffer);
+  gtk_text_buffer_delete (buffer, &begin, &end);
+  gtk_text_buffer_end_user_action (buffer);
+
+  vim->priv->target_line_offset = gb_editor_vim_get_line_offset (vim);
+
+  insert = gtk_text_buffer_get_insert (buffer);
+  gtk_text_view_scroll_mark_onscreen (vim->priv->text_view, insert);
+}
+
+static void
+gb_editor_vim_select_line (GbEditorVim *vim)
+{
+  GbEditorVimPrivate *priv;
+  GtkTextBuffer *buffer;
+  GtkTextMark *insert;
+  GtkTextIter iter;
+  GtkTextIter begin;
+  GtkTextIter end;
+
+  g_assert (GB_IS_EDITOR_VIM (vim));
+
+  priv = vim->priv;
+
+  buffer = gtk_text_view_get_buffer (priv->text_view);
+  insert = gtk_text_buffer_get_insert (buffer);
+  gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
+
+  gtk_text_iter_assign (&begin, &iter);
+  while (!gtk_text_iter_starts_line (&begin))
+    if (!gtk_text_iter_backward_char (&begin))
+      break;
+
+  gtk_text_iter_assign (&end, &iter);
+  while (!gtk_text_iter_ends_line (&end))
+    if (!gtk_text_iter_forward_char (&end))
+      break;
+
+  gtk_text_buffer_select_range (buffer, &begin, &end);
+}
+
+static void
+gb_editor_vim_undo (GbEditorVim *vim)
+{
+  GtkSourceUndoManager *undo;
+  GtkTextBuffer *buffer;
+  GtkTextMark *insert;
+
+  g_assert (GB_IS_EDITOR_VIM (vim));
+
+  /*
+   * We only support GtkSourceView for now.
+   */
+  buffer = gtk_text_view_get_buffer (vim->priv->text_view);
+  if (!GTK_SOURCE_IS_BUFFER (buffer))
+    return;
+
+  undo = gtk_source_buffer_get_undo_manager (GTK_SOURCE_BUFFER (buffer));
+  if (gtk_source_undo_manager_can_undo (undo))
+    gtk_source_undo_manager_undo (undo);
+
+  vim->priv->target_line_offset = gb_editor_vim_get_line_offset (vim);
+
+  insert = gtk_text_buffer_get_insert (buffer);
+  gtk_text_view_scroll_mark_onscreen (vim->priv->text_view, insert);
+}
+
+static void
+gb_editor_vim_redo (GbEditorVim *vim)
+{
+  GtkSourceUndoManager *undo;
+  GtkTextBuffer *buffer;
+  GtkTextMark *insert;
+
+  g_assert (GB_IS_EDITOR_VIM (vim));
+
+  /*
+   * We only support GtkSourceView for now.
+   */
+  buffer = gtk_text_view_get_buffer (vim->priv->text_view);
+  if (!GTK_SOURCE_IS_BUFFER (buffer))
+    return;
+
+  undo = gtk_source_buffer_get_undo_manager (GTK_SOURCE_BUFFER (buffer));
+  if (gtk_source_undo_manager_can_redo (undo))
+    gtk_source_undo_manager_redo (undo);
+
+  vim->priv->target_line_offset = gb_editor_vim_get_line_offset (vim);
+
+  insert = gtk_text_buffer_get_insert (buffer);
+  gtk_text_view_scroll_mark_onscreen (vim->priv->text_view, insert);
+}
+
+static gboolean
+gb_editor_vim_handle_normal (GbEditorVim *vim,
+                             GdkEventKey *event)
+{
+  /*
+   * WORKAROUND:
+   *
+   * The masks above are due to how the X server sometimes reports back
+   * invalid modifier keys. Not ideal, but seems to get us moving.
+   */
+
+  g_assert (GB_IS_EDITOR_VIM (vim));
+  g_assert (event);
+
+  switch (event->keyval)
+    {
+    case GDK_KEY_I:
+      /*
+       * Start insert mode at the beginning of the line.
+       */
+      gb_editor_vim_move_line_start (vim);
+      gb_editor_vim_set_mode (vim, GB_EDITOR_VIM_INSERT);
+      return TRUE;
+
+    case GDK_KEY_i:
+      /*
+       * Start insert mode at the current line position.
+       */
+      gb_editor_vim_set_mode (vim, GB_EDITOR_VIM_INSERT);
+      return TRUE;
+
+    case GDK_KEY_A:
+      /*
+       * Start insert mode at the end of the line.
+       */
+      gb_editor_vim_move_line_end (vim);
+      gb_editor_vim_set_mode (vim, GB_EDITOR_VIM_INSERT);
+      return TRUE;
+
+    case GDK_KEY_a:
+      /*
+       * Start insert mode at the beginning of the line.
+       */
+      gb_editor_vim_move_forward (vim);
+      gb_editor_vim_set_mode (vim, GB_EDITOR_VIM_INSERT);
+      return TRUE;
+
+    case GDK_KEY_l:
+      /*
+       * Move forward in the buffer one character, but stay on the
+       * same line.
+       */
+      gb_editor_vim_move_forward (vim);
+      return TRUE;
+
+    case GDK_KEY_h:
+      /*
+       * Move backward in the buffer one character, but stay on the
+       * same line.
+       */
+      gb_editor_vim_move_backward (vim);
+      return TRUE;
+
+    case GDK_KEY_j:
+      /*
+       * Move down in the buffer one line, and try to stay on the same column.
+       */
+      gb_editor_vim_move_down (vim);
+      return TRUE;
+
+    case GDK_KEY_k:
+      /*
+       * Move down in the buffer one line, and try to stay on the same column.
+       */
+      gb_editor_vim_move_up (vim);
+      return TRUE;
+
+    case GDK_KEY_V:
+      /*
+       * Select the current line.
+       *
+       * TODO: Allow this selection to grow. Might want to just add another
+       *        mode for selections like VIM does.
+       */
+      gb_editor_vim_select_line (vim);
+      return TRUE;
+
+    case GDK_KEY_w:
+      /*
+       * Move forward by one word.
+       */
+      gb_editor_vim_move_forward_word (vim);
+      return TRUE;
+
+    case GDK_KEY_b:
+      /*
+       * Move backward by one word.
+       */
+      gb_editor_vim_move_backward_word (vim);
+      return TRUE;
+
+    case GDK_KEY_x:
+      /*
+       * Delete the current selection.
+       */
+      gb_editor_vim_delete_selection (vim);
+      break;
+
+    case GDK_KEY_u:
+      /*
+       * Undo the last operation if we can.
+       */
+      gb_editor_vim_undo (vim);
+      break;
+
+    case GDK_KEY_O:
+      /*
+       * TODO: Insert a newline before the current line, and start editing.
+       */
+      break;
+
+    case GDK_KEY_o:
+      /*
+       * TODO: Insert a new line, and then begin insertion.
+       */
+      break;
+
+    case GDK_KEY_r:
+      /*
+       * Try to redo a previously undone operation if we can.
+       */
+      if ((event->state & GDK_CONTROL_MASK))
+        {
+          gb_editor_vim_redo (vim);
+          return TRUE;
+        }
+
+      break;
+
+    default:
+      break;
+    }
+
+  if (event->string && *event->string)
+    return TRUE;
+
+  return FALSE;
+}
+
+static gboolean
+gb_editor_vim_handle_insert (GbEditorVim *vim,
+                             GdkEventKey *event)
+{
+  if (event->keyval == GDK_KEY_Escape)
+    {
+      gb_editor_vim_set_mode (vim, GB_EDITOR_VIM_NORMAL);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+gb_editor_vim_handle_command (GbEditorVim *vim,
+                              GdkEventKey *event)
+{
+  return FALSE;
+}
+
+static gboolean
+gb_editor_vim_key_press_event_cb (GtkTextView *text_view,
+                                  GdkEventKey *event,
+                                  GbEditorVim *vim)
+{
+  gboolean ret;
+
+  ENTRY;
+
+  g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), FALSE);
+  g_return_val_if_fail (event, FALSE);
+  g_return_val_if_fail (GB_IS_EDITOR_VIM (vim), FALSE);
+
+  switch (vim->priv->mode)
+    {
+    case GB_EDITOR_VIM_NORMAL:
+      ret = gb_editor_vim_handle_normal (vim, event);
+      RETURN (ret);
+
+    case GB_EDITOR_VIM_INSERT:
+      ret = gb_editor_vim_handle_insert (vim, event);
+      RETURN (ret);
+
+    case GB_EDITOR_VIM_COMMAND:
+      ret = gb_editor_vim_handle_command (vim, event);
+      RETURN (ret);
+
+    default:
+      g_assert_not_reached();
+    }
+
+  RETURN (FALSE);
+}
+
+static void
+gb_editor_vim_connect (GbEditorVim *vim)
+{
+  g_return_if_fail (GB_IS_EDITOR_VIM (vim));
+
+  vim->priv->key_press_event_handler =
+    g_signal_connect (vim->priv->text_view,
+                      "key-press-event",
+                      G_CALLBACK (gb_editor_vim_key_press_event_cb),
+                      vim);
+}
+
+static void
+gb_editor_vim_disconnect (GbEditorVim *vim)
+{
+  g_return_if_fail (GB_IS_EDITOR_VIM (vim));
+
+  g_signal_handler_disconnect (vim->priv->text_view,
+                               vim->priv->key_press_event_handler);
+  vim->priv->key_press_event_handler = 0;
+}
+
+gboolean
+gb_editor_vim_get_enabled (GbEditorVim *vim)
+{
+  g_return_val_if_fail (GB_IS_EDITOR_VIM (vim), FALSE);
+
+  return vim->priv->enabled;
+}
+
+void
+gb_editor_vim_set_enabled (GbEditorVim *vim,
+                           gboolean     enabled)
+{
+  GbEditorVimPrivate *priv;
+
+  g_return_if_fail (GB_IS_EDITOR_VIM (vim));
+
+  priv = vim->priv;
+
+  if (priv->enabled == enabled)
+    return;
+
+  if (enabled)
+    {
+      gb_editor_vim_connect (vim);
+      priv->enabled = TRUE;
+    }
+  else
+    {
+      gb_editor_vim_disconnect (vim);
+      priv->enabled = FALSE;
+    }
+
+  g_object_notify_by_pspec (G_OBJECT (vim), gParamSpecs [PROP_ENABLED]);
+}
+
+GtkWidget *
+gb_editor_vim_get_text_view (GbEditorVim *vim)
+{
+  g_return_val_if_fail (GB_IS_EDITOR_VIM (vim), NULL);
+
+  return (GtkWidget *)vim->priv->text_view;
+}
+
+static void
+gb_editor_vim_set_text_view (GbEditorVim *vim,
+                             GtkTextView *text_view)
+{
+  GbEditorVimPrivate *priv;
+
+  g_return_if_fail (GB_IS_EDITOR_VIM (vim));
+  g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
+
+  priv = vim->priv;
+
+  if (priv->text_view == text_view)
+    return;
+
+  if (priv->text_view)
+    {
+      if (priv->enabled)
+        gb_editor_vim_disconnect (vim);
+      g_object_remove_weak_pointer (G_OBJECT (priv->text_view),
+                                    (gpointer *)&priv->text_view);
+      priv->text_view = NULL;
+    }
+
+  if (text_view)
+    {
+      priv->text_view = text_view;
+      g_object_add_weak_pointer (G_OBJECT (text_view),
+                                 (gpointer *)&priv->text_view);
+      if (priv->enabled)
+        gb_editor_vim_connect (vim);
+    }
+
+  g_object_notify_by_pspec (G_OBJECT (vim), gParamSpecs [PROP_TEXT_VIEW]);
+}
+
+static void
+gb_editor_vim_finalize (GObject *object)
+{
+  GbEditorVimPrivate *priv = GB_EDITOR_VIM (object)->priv;
+
+  if (priv->text_view)
+    {
+      g_object_remove_weak_pointer (G_OBJECT (priv->text_view),
+                                    (gpointer *)&priv->text_view);
+      priv->text_view = NULL;
+    }
+
+  G_OBJECT_CLASS (gb_editor_vim_parent_class)->finalize (object);
+}
+
+static void
+gb_editor_vim_get_property (GObject    *object,
+                            guint       prop_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
+{
+  GbEditorVim *vim = GB_EDITOR_VIM (object);
+
+  switch (prop_id)
+    {
+    case PROP_ENABLED:
+      g_value_set_boolean (value, gb_editor_vim_get_enabled (vim));
+      break;
+
+    case PROP_MODE:
+      g_value_set_enum (value, gb_editor_vim_get_mode (vim));
+      break;
+
+    case PROP_TEXT_VIEW:
+      g_value_set_object (value, gb_editor_vim_get_text_view (vim));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gb_editor_vim_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  GbEditorVim *vim = GB_EDITOR_VIM (object);
+
+  switch (prop_id)
+    {
+    case PROP_ENABLED:
+      gb_editor_vim_set_enabled (vim, g_value_get_boolean (value));
+      break;
+
+    case PROP_TEXT_VIEW:
+      gb_editor_vim_set_text_view (vim, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gb_editor_vim_class_init (GbEditorVimClass *klass)
+{
+  GObjectClass *object_class;
+
+  object_class = G_OBJECT_CLASS (klass);
+  object_class->finalize = gb_editor_vim_finalize;
+  object_class->get_property = gb_editor_vim_get_property;
+  object_class->set_property = gb_editor_vim_set_property;
+
+  gParamSpecs [PROP_ENABLED] =
+    g_param_spec_boolean ("enabled",
+                          _("Enabled"),
+                          _("If the VIM engine is enabled."),
+                          FALSE,
+                          (G_PARAM_READWRITE |
+                           G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_ENABLED,
+                                   gParamSpecs [PROP_ENABLED]);
+
+  gParamSpecs [PROP_MODE] =
+    g_param_spec_enum ("mode",
+                       _("Mode"),
+                       _("The current mode of the widget."),
+                       GB_TYPE_EDITOR_VIM_MODE,
+                       GB_EDITOR_VIM_NORMAL,
+                       (G_PARAM_READABLE |
+                        G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_MODE,
+                                   gParamSpecs [PROP_MODE]);
+
+  gParamSpecs [PROP_TEXT_VIEW] =
+    g_param_spec_object ("text-view",
+                         _("Text View"),
+                         _("The text view the VIM engine is managing."),
+                         GTK_TYPE_TEXT_VIEW,
+                         (G_PARAM_READWRITE |
+                          G_PARAM_CONSTRUCT_ONLY |
+                          G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_TEXT_VIEW,
+                                   gParamSpecs [PROP_TEXT_VIEW]);
+}
+
+static void
+gb_editor_vim_init (GbEditorVim *vim)
+{
+  vim->priv = gb_editor_vim_get_instance_private (vim);
+  vim->priv->enabled = FALSE;
+  vim->priv->mode = GB_EDITOR_VIM_NORMAL;
+}
+
+GType
+gb_editor_vim_mode_get_type (void)
+{
+  static GType type_id;
+  static const GEnumValue values[] = {
+    { GB_EDITOR_VIM_NORMAL, "GB_EDITOR_VIM_NORMAL", "NORMAL" },
+    { GB_EDITOR_VIM_INSERT, "GB_EDITOR_VIM_INSERT", "INSERT" },
+    { GB_EDITOR_VIM_COMMAND, "GB_EDITOR_VIM_COMMAND", "COMMAND" },
+    { 0 }
+  };
+
+  if (!type_id)
+    type_id = g_enum_register_static ("GbEditorVimMode", values);
+
+  return type_id;
+}
diff --git a/src/editor/gb-editor-vim.h b/src/editor/gb-editor-vim.h
new file mode 100644
index 0000000..a775579
--- /dev/null
+++ b/src/editor/gb-editor-vim.h
@@ -0,0 +1,70 @@
+/* gb-editor-vim.h
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GB_EDITOR_VIM_H
+#define GB_EDITOR_VIM_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_EDITOR_VIM            (gb_editor_vim_get_type())
+#define GB_TYPE_EDITOR_VIM_MODE       (gb_editor_vim_mode_get_type())
+#define GB_EDITOR_VIM(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GB_TYPE_EDITOR_VIM, GbEditorVim))
+#define GB_EDITOR_VIM_CONST(obj)      (G_TYPE_CHECK_INSTANCE_CAST ((obj), GB_TYPE_EDITOR_VIM, GbEditorVim 
const))
+#define GB_EDITOR_VIM_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  GB_TYPE_EDITOR_VIM, 
GbEditorVimClass))
+#define GB_IS_EDITOR_VIM(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GB_TYPE_EDITOR_VIM))
+#define GB_IS_EDITOR_VIM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  GB_TYPE_EDITOR_VIM))
+#define GB_EDITOR_VIM_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  GB_TYPE_EDITOR_VIM, 
GbEditorVimClass))
+
+typedef struct _GbEditorVim        GbEditorVim;
+typedef struct _GbEditorVimClass   GbEditorVimClass;
+typedef struct _GbEditorVimPrivate GbEditorVimPrivate;
+
+typedef enum
+{
+  GB_EDITOR_VIM_NORMAL,
+  GB_EDITOR_VIM_INSERT,
+  GB_EDITOR_VIM_COMMAND,
+} GbEditorVimMode;
+
+struct _GbEditorVim
+{
+  GObject parent;
+
+  /*< private >*/
+  GbEditorVimPrivate *priv;
+};
+
+struct _GbEditorVimClass
+{
+  GObjectClass parent_class;
+};
+
+GType            gb_editor_vim_get_type      (void) G_GNUC_CONST;
+GType            gb_editor_vim_mode_get_type (void) G_GNUC_CONST;
+GbEditorVim     *gb_editor_vim_new           (GtkTextView  *text_view);
+GbEditorVimMode  gb_editor_vim_get_mode      (GbEditorVim  *vim);
+gboolean         gb_editor_vim_get_enabled   (GbEditorVim  *vim);
+void             gb_editor_vim_set_enabled   (GbEditorVim  *vim,
+                                              gboolean      enabled);
+GtkWidget       *gb_editor_vim_get_text_view (GbEditorVim  *vim);
+
+G_END_DECLS
+
+#endif /* GB_EDITOR_VIM_H */
diff --git a/src/editor/gb-source-view.h b/src/editor/gb-source-view.h
index c9cc9db..f27e218 100644
--- a/src/editor/gb-source-view.h
+++ b/src/editor/gb-source-view.h
@@ -58,15 +58,13 @@ struct _GbSourceViewClass
                         GbSourceSnippet        *snippet);
 };
 
-GType gb_source_view_get_type       (void) G_GNUC_CONST;
-void  gb_source_view_push_snippet   (GbSourceView      *view,
-                                     GbSourceSnippet   *snippet);
-void  gb_source_view_clear_snippets (GbSourceView      *view);
-
-gboolean gb_source_view_get_show_shadow (GbSourceView *view); /* XXX: Remove this */
-void     gb_source_view_set_show_shadow (GbSourceView *view, /* XXX: Remove this */
-                                         gboolean      show_shadow);
-
+GType                 gb_source_view_get_type          (void) G_GNUC_CONST;
+void                  gb_source_view_push_snippet      (GbSourceView         *view,
+                                                        GbSourceSnippet      *snippet);
+void                  gb_source_view_clear_snippets    (GbSourceView         *view);
+gboolean              gb_source_view_get_show_shadow   (GbSourceView         *view);
+void                  gb_source_view_set_show_shadow   (GbSourceView         *view,
+                                                        gboolean              show_shadow);
 GbSourceAutoIndenter *gb_source_view_get_auto_indenter (GbSourceView         *view);
 void                  gb_source_view_set_auto_indenter (GbSourceView         *view,
                                                         GbSourceAutoIndenter *auto_indenter);
diff --git a/src/gnome-builder.mk b/src/gnome-builder.mk
index 31b5c97..60e0e77 100644
--- a/src/gnome-builder.mk
+++ b/src/gnome-builder.mk
@@ -28,6 +28,8 @@ libgnome_builder_la_SOURCES = \
        src/editor/gb-editor-tab.c \
        src/editor/gb-editor-tab.h \
        src/editor/gb-editor-tab-private.h \
+       src/editor/gb-editor-vim.c \
+       src/editor/gb-editor-vim.h \
        src/editor/gb-editor-workspace.c \
        src/editor/gb-editor-workspace.h \
        src/editor/gb-editor-workspace-private.h \


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