[gtksourceview/wip/chergert/vim: 20/21] wip on vim input method




commit e150ac2a325a0c53177a75b371ba40f7d5b352d5
Author: Christian Hergert <chergert redhat com>
Date:   Mon Oct 18 15:42:54 2021 -0700

    wip on vim input method

 gtksourceview/gtksource.h              |   1 +
 gtksourceview/gtksourcetypes-private.h |   2 +
 gtksourceview/gtksourcevim-private.h   |  68 ++++++
 gtksourceview/gtksourcevim.c           | 337 +++++++++++++++++++++++++++++
 gtksourceview/gtksourcevimimcontext.c  | 381 +++++++++++++++++++++++++++++++++
 gtksourceview/gtksourcevimimcontext.h  |  50 +++++
 gtksourceview/meson.build              |   3 +
 tests/meson.build                      |   1 +
 tests/test-vim.c                       |  99 +++++++++
 9 files changed, 942 insertions(+)
---
diff --git a/gtksourceview/gtksource.h b/gtksourceview/gtksource.h
index 8d7c97b0..be1e07c3 100644
--- a/gtksourceview/gtksource.h
+++ b/gtksourceview/gtksource.h
@@ -67,6 +67,7 @@
 #include "gtksourceutils.h"
 #include "gtksourceversion.h"
 #include "gtksourceview.h"
+#include "gtksourcevimimcontext.h"
 #include "gtksource-enumtypes.h"
 
 #include "completion-providers/words/gtksourcecompletionwords.h"
diff --git a/gtksourceview/gtksourcetypes-private.h b/gtksourceview/gtksourcetypes-private.h
index cde07715..669bbc0a 100644
--- a/gtksourceview/gtksourcetypes-private.h
+++ b/gtksourceview/gtksourcetypes-private.h
@@ -39,6 +39,8 @@ typedef struct _GtkSourceMarksSequence          GtkSourceMarksSequence;
 typedef struct _GtkSourcePixbufHelper           GtkSourcePixbufHelper;
 typedef struct _GtkSourceRegex                  GtkSourceRegex;
 typedef struct _GtkSourceSnippetBundle          GtkSourceSnippetBundle;
+typedef struct _GtkSourceVim                    GtkSourceVim;
+typedef struct _GtkSourceVimCallbacks           GtkSourceVimCallbacks;
 
 #ifdef _MSC_VER
 /* For Visual Studio, we need to export the symbols used by the unit tests */
diff --git a/gtksourceview/gtksourcevim-private.h b/gtksourceview/gtksourcevim-private.h
new file mode 100644
index 00000000..8c0d186c
--- /dev/null
+++ b/gtksourceview/gtksourcevim-private.h
@@ -0,0 +1,68 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#pragma once
+
+#include <gdk/gdk.h>
+
+#include "gtksourcetypes-private.h"
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+       GTK_SOURCE_VIM_NORMAL = 1,
+       GTK_SOURCE_VIM_INSERT,
+       GTK_SOURCE_VIM_COMMAND_LINE,
+       GTK_SOURCE_VIM_SPECIAL_CHAR_PENDING,
+       GTK_SOURCE_VIM_REPLACE,
+       GTK_SOURCE_VIM_VIRTUAL_REPLACE,
+       GTK_SOURCE_VIM_VISUAL,
+       GTK_SOURCE_VIM_VISUAL_LINE,
+       GTK_SOURCE_VIM_VISUAL_BLOCK,
+} GtkSourceVimMode;
+
+typedef enum
+{
+       GTK_SOURCE_VIM_LABEL_COMMAND = 1,
+       GTK_SOURCE_VIM_LABEL_COMMAND_BAR,
+} GtkSourceVimLabel;
+
+struct _GtkSourceVimCallbacks
+{
+       void     (*beep)            (gpointer           user_data);
+       void     (*set_mode)        (GtkSourceVimMode   mode,
+                                    gpointer           user_data);
+       void     (*set_label)       (GtkSourceVimLabel  label,
+                                    const char        *text,
+                                    gpointer           user_data);
+       gboolean (*filter_keypress) (GdkEvent          *event,
+                                    gpointer           user_data);
+};
+
+GtkSourceVim *gtk_source_vim_new     (const GtkSourceVimCallbacks *callbacks,
+                                      gpointer                     user_data);
+gboolean      gtk_source_vim_deliver (GtkSourceVim                *self,
+                                     GdkEvent                    *event);
+void          gtk_source_vim_reset   (GtkSourceVim                *self);
+void          gtk_source_vim_free    (GtkSourceVim                *self);
+
+G_END_DECLS
diff --git a/gtksourceview/gtksourcevim.c b/gtksourceview/gtksourcevim.c
new file mode 100644
index 00000000..ce52b06b
--- /dev/null
+++ b/gtksourceview/gtksourcevim.c
@@ -0,0 +1,337 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gtksourcevim-private.h"
+
+typedef struct
+{
+       GList             link;
+       GtkSourceVimMode  mode;
+       GString          *command_label;
+       GString          *command_bar_label;
+       int               count;
+} GtkSourceVimState;
+
+struct _GtkSourceVim
+{
+       GtkSourceVimCallbacks callbacks;
+       gpointer              callback_data;
+       GQueue                state;
+};
+
+static inline gboolean
+key_is_escape (guint           keyval,
+               guint           keycode,
+               GdkModifierType state)
+{
+       return keyval == GDK_KEY_Escape ||
+               (keyval == GDK_KEY_bracketleft && (state & GDK_CONTROL_MASK) != 0);
+}
+
+static inline gboolean
+gtk_source_vim_filter_keypress (GtkSourceVim *self,
+                                GdkEvent     *event)
+{
+       g_assert (self != NULL);
+       g_assert (event != NULL);
+
+       if (self->callbacks.filter_keypress)
+       {
+               return self->callbacks.filter_keypress (event, self->callback_data);
+       }
+
+       return FALSE;
+}
+
+static inline void
+gtk_source_vim_beep (GtkSourceVim *self)
+{
+       g_assert (self != NULL);
+
+       if (self->callbacks.beep)
+       {
+               self->callbacks.beep (self->callback_data);
+       }
+}
+
+static inline void
+gtk_source_vim_notify_labels (GtkSourceVim *self)
+{
+       GtkSourceVimState *state;
+
+       g_assert (self != NULL);
+       g_assert (self->state.length > 0);
+
+       state = g_queue_peek_tail (&self->state);
+
+       if (self->callbacks.set_label)
+       {
+               self->callbacks.set_label (GTK_SOURCE_VIM_LABEL_COMMAND, state->command_label->str, 
self->callback_data);
+               self->callbacks.set_label (GTK_SOURCE_VIM_LABEL_COMMAND_BAR, state->command_bar_label->str, 
self->callback_data);
+       }
+}
+
+static void
+gtk_source_vim_repeat (GtkSourceVim      *self,
+                       GtkSourceVimState *state,
+                       int                count)
+{
+       g_assert (self != NULL);
+       g_assert (state != NULL);
+
+       /* TODO: */
+}
+
+static void
+gtk_source_vim_state_free (GtkSourceVimState *state)
+{
+       g_assert (state != NULL);
+       g_assert (state->mode > 0);
+       g_assert (state->link.prev == NULL);
+       g_assert (state->link.next == NULL);
+       g_assert (state->link.data == state);
+
+       g_string_free (state->command_label, TRUE);
+       g_string_free (state->command_bar_label, TRUE);
+
+       state->command_label = NULL;
+       state->command_bar_label = NULL;
+
+       g_free (state);
+}
+
+static GtkSourceVimState *
+gtk_source_vim_push (GtkSourceVim     *self,
+                     GtkSourceVimMode  mode)
+{
+       GtkSourceVimState *state;
+
+       g_assert (self != NULL);
+       g_assert (mode > 0);
+
+       state = g_new0 (GtkSourceVimState, 1);
+       state->mode = mode;
+       state->link.data = state;
+       state->command_label = g_string_new (NULL);
+       state->command_bar_label = g_string_new (NULL);
+
+       g_queue_push_tail_link (&self->state, &state->link);
+
+       if (self->callbacks.set_mode)
+               self->callbacks.set_mode (mode, self->callback_data);
+
+       gtk_source_vim_notify_labels (self);
+
+       return state;
+}
+
+static void
+gtk_source_vim_pop (GtkSourceVim *self)
+{
+       GtkSourceVimState *popped, *state;
+
+       g_assert (self != NULL);
+       g_assert (self->state.length > 0);
+
+       popped = g_queue_peek_tail (&self->state);
+       g_queue_unlink (&self->state, &popped->link);
+
+       if ((state = g_queue_peek_tail (&self->state)))
+       {
+               /* If our new state has a repeat, we might need to
+                * repeat the previous state multiple times.
+                */
+               if (state->count > 1)
+               {
+                       gtk_source_vim_repeat (self, popped, state->count - 1);
+               }
+
+               if (self->callbacks.set_mode)
+               {
+                       self->callbacks.set_mode (state->mode, self->callback_data);
+               }
+
+               gtk_source_vim_notify_labels (self);
+       }
+
+       gtk_source_vim_state_free (popped);
+}
+
+GtkSourceVim *
+gtk_source_vim_new (const GtkSourceVimCallbacks *callbacks,
+                    gpointer                     user_data)
+{
+       GtkSourceVim *self;
+
+       g_return_val_if_fail (callbacks != NULL, NULL);
+
+       self = g_new0 (GtkSourceVim, 1);
+       self->callbacks = *callbacks;
+       self->callback_data = user_data;
+
+       gtk_source_vim_reset (self);
+
+       return self;
+}
+
+void
+gtk_source_vim_free (GtkSourceVim *self)
+{
+       if (self != NULL)
+       {
+               while (self->state.length > 0)
+               {
+                       gtk_source_vim_pop (self);
+               }
+
+               g_free (self);
+       }
+}
+
+void
+gtk_source_vim_reset (GtkSourceVim *self)
+{
+       g_return_if_fail (self != NULL);
+
+       while (self->state.length > 0)
+       {
+               GtkSourceVimState *state = g_queue_pop_tail (&self->state);
+               g_queue_unlink (&self->state, &state->link);
+               gtk_source_vim_state_free (state);
+       }
+
+       gtk_source_vim_push (self, GTK_SOURCE_VIM_NORMAL);
+}
+
+static gboolean
+gtk_source_vim_handle_normal (GtkSourceVim      *self,
+                             GtkSourceVimState *state,
+                             guint              keyval,
+                             GdkModifierType    mods)
+{
+       g_assert (self != NULL);
+       g_assert (state != NULL);
+       g_assert (state->mode == GTK_SOURCE_VIM_NORMAL);
+
+       switch (keyval)
+       {
+       case GDK_KEY_0: case GDK_KEY_1: case GDK_KEY_2: case GDK_KEY_3:
+       case GDK_KEY_4: case GDK_KEY_5: case GDK_KEY_6: case GDK_KEY_7:
+       case GDK_KEY_8: case GDK_KEY_9:
+               state->count *= 10;
+               state->count += keyval - GDK_KEY_0;
+               return TRUE;
+
+       case GDK_KEY_h: case GDK_KEY_Left:
+       case GDK_KEY_l: case GDK_KEY_Right:
+       case GDK_KEY_k: case GDK_KEY_Up:
+       case GDK_KEY_j: case GDK_KEY_Down:
+               return TRUE;
+
+       case GDK_KEY_i:
+               gtk_source_vim_push (self, GTK_SOURCE_VIM_INSERT);
+               return TRUE;
+
+       default:
+               gtk_source_vim_beep (self);
+               return TRUE;
+       }
+}
+
+gboolean
+gtk_source_vim_deliver (GtkSourceVim *self,
+                        GdkEvent     *event)
+{
+       GtkSourceVimState *state;
+       GdkEventType event_type;
+       GdkModifierType mods;
+       guint keyval, keycode;
+
+       g_return_val_if_fail (self != NULL, FALSE);
+       g_return_val_if_fail (self->state.length > 0, FALSE);
+
+       state = g_queue_peek_tail (&self->state);
+
+       event_type = gdk_event_get_event_type (event);
+       keyval = gdk_key_event_get_keyval (event);
+       keycode = gdk_key_event_get_keycode (event);
+       mods = gdk_key_event_get_consumed_modifiers (event);
+
+       /* Try to pass things along to the native input method when
+        * we are in insert mode, only processing things after that.
+        * Otherwise we might not be able to exit from the input method
+        * dialogs easily.
+        */
+       if (state->mode == GTK_SOURCE_VIM_INSERT &&
+           gtk_source_vim_filter_keypress (self, event))
+       {
+               return TRUE;
+       }
+
+       /* Now we only care about keypress events */
+       if (event_type != GDK_KEY_PRESS)
+       {
+               return FALSE;
+       }
+
+       switch (state->mode)
+       {
+       case GTK_SOURCE_VIM_INSERT:
+
+               if (key_is_escape (keyval, keycode, mods))
+               {
+                       gtk_source_vim_pop (self);
+                       return TRUE;
+               }
+
+               return FALSE;
+
+       case GTK_SOURCE_VIM_NORMAL:
+
+               if (key_is_escape (keyval, keycode, mods))
+               {
+                       gtk_source_vim_pop (self);
+
+                       if (self->state.length == 0)
+                       {
+                               gtk_source_vim_push (self, GTK_SOURCE_VIM_NORMAL);
+                       }
+
+                       return TRUE;
+               }
+
+               return gtk_source_vim_handle_normal (self, state, keyval, mods);
+
+       case GTK_SOURCE_VIM_COMMAND_LINE:
+       case GTK_SOURCE_VIM_SPECIAL_CHAR_PENDING:
+       case GTK_SOURCE_VIM_REPLACE:
+       case GTK_SOURCE_VIM_VIRTUAL_REPLACE:
+       case GTK_SOURCE_VIM_VISUAL:
+       case GTK_SOURCE_VIM_VISUAL_LINE:
+       case GTK_SOURCE_VIM_VISUAL_BLOCK:
+       default:
+               break;
+       }
+
+       return FALSE;
+}
diff --git a/gtksourceview/gtksourcevimimcontext.c b/gtksourceview/gtksourcevimimcontext.c
new file mode 100644
index 00000000..1bbbff5b
--- /dev/null
+++ b/gtksourceview/gtksourcevimimcontext.c
@@ -0,0 +1,381 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gtksourceview.h"
+#include "gtksourcevimimcontext.h"
+#include "gtksourcevim-private.h"
+
+#include "gtksource-enumtypes.h"
+
+struct _GtkSourceVimIMContext
+{
+       GtkIMContext      parent_instance;
+       GtkSourceView    *view;
+       GtkSourceVim     *vim;
+       char             *command_bar_text;
+       char             *command_text;
+       GtkSourceVimMode  mode;
+};
+
+G_DEFINE_TYPE (GtkSourceVimIMContext, gtk_source_vim_im_context, GTK_TYPE_IM_CONTEXT)
+
+enum {
+       PROP_0,
+       PROP_COMMAND_BAR_TEXT,
+       PROP_COMMAND_TEXT,
+       PROP_MODE,
+       PROP_VIEW,
+       N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+GtkIMContext *
+gtk_source_vim_im_context_new (void)
+{
+       return g_object_new (GTK_SOURCE_TYPE_VIM_IM_CONTEXT, NULL);
+}
+
+static void
+gtk_source_vim_im_context_set_client_widget (GtkIMContext *context,
+                                             GtkWidget    *widget)
+{
+       GtkSourceVimIMContext *self = (GtkSourceVimIMContext *)context;
+
+       g_return_if_fail (GTK_SOURCE_IS_VIM_IM_CONTEXT (self));
+
+       g_set_weak_pointer (&self->view, GTK_SOURCE_VIEW (widget));
+}
+
+static void
+gtk_source_vim_im_context_reset (GtkIMContext *context)
+{
+       GtkSourceVimIMContext *self = (GtkSourceVimIMContext *)context;
+
+       g_return_if_fail (GTK_SOURCE_IS_VIM_IM_CONTEXT (self));
+
+       gtk_source_vim_reset (self->vim);
+}
+
+static void
+gtk_source_vim_im_context_focus_in (GtkIMContext *context)
+{
+       GtkSourceVimIMContext *self = (GtkSourceVimIMContext *)context;
+
+       g_return_if_fail (GTK_SOURCE_IS_VIM_IM_CONTEXT (self));
+
+       gtk_text_view_set_overwrite (GTK_TEXT_VIEW (self->view), TRUE);
+       gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (self->view), TRUE);
+
+       g_print ("Focus in %p\n", self->view);
+}
+
+static void
+gtk_source_vim_im_context_focus_out (GtkIMContext *context)
+{
+       GtkSourceVimIMContext *self = (GtkSourceVimIMContext *)context;
+
+       g_return_if_fail (GTK_SOURCE_IS_VIM_IM_CONTEXT (self));
+
+       gtk_text_view_set_overwrite (GTK_TEXT_VIEW (self->view), FALSE);
+       gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (self->view), FALSE);
+
+       g_print ("Focus out\n");
+}
+
+static void
+gtk_source_vim_im_context_beep (gpointer user_data)
+{
+       GtkSourceVimIMContext *self = user_data;
+       GdkDisplay *display;
+
+       g_assert (GTK_SOURCE_IS_VIM_IM_CONTEXT (self));
+
+       if (self->view != NULL &&
+           (display = gtk_widget_get_display (GTK_WIDGET (self->view))))
+       {
+               gdk_display_beep (display);
+       }
+}
+
+static void
+gtk_source_vim_im_context_set_mode (GtkSourceVimMode mode,
+                                    gpointer         user_data)
+{
+       GtkSourceVimIMContext *self = user_data;
+
+       g_assert (GTK_SOURCE_IS_VIM_IM_CONTEXT (self));
+
+       self->mode = mode;
+       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MODE]);
+}
+
+static void
+gtk_source_vim_im_context_set_label (GtkSourceVimLabel  label,
+                                     const char        *text,
+                                     gpointer           user_data)
+{
+       GtkSourceVimIMContext *self = user_data;
+
+       g_assert (GTK_SOURCE_IS_VIM_IM_CONTEXT (self));
+
+       if (label == GTK_SOURCE_VIM_LABEL_COMMAND_BAR)
+       {
+               g_free (self->command_bar_text);
+               self->command_bar_text = g_strdup (text);
+               g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_COMMAND_BAR_TEXT]);
+       }
+       else if (label == GTK_SOURCE_VIM_LABEL_COMMAND)
+       {
+               g_free (self->command_text);
+               self->command_text = g_strdup (text);
+               g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_COMMAND_TEXT]);
+       }
+}
+
+static gboolean
+gtk_source_vim_im_context_propagate_keypress (GdkEvent *event,
+                                              gpointer  user_data)
+{
+       GtkSourceVimIMContext *self = user_data;
+
+       g_assert (GTK_SOURCE_IS_VIM_IM_CONTEXT (self));
+
+       if (self->view == NULL)
+       {
+               return FALSE;
+       }
+
+       return gtk_text_view_im_context_filter_keypress (GTK_TEXT_VIEW (self->view), event);
+}
+
+static gboolean
+gtk_source_vim_im_context_filter_keypress (GtkIMContext *context,
+                                           GdkEvent     *event)
+{
+       GtkSourceVimIMContext *self = (GtkSourceVimIMContext *)context;
+       GdkModifierType state;
+       gboolean ret = FALSE;
+       guint keyval;
+
+       g_assert (GTK_SOURCE_IS_VIM_IM_CONTEXT (self));
+       g_assert (gdk_event_get_event_type (event) == GDK_KEY_PRESS ||
+                 gdk_event_get_event_type (event) == GDK_KEY_RELEASE);
+
+       return gtk_source_vim_deliver (self->vim, event);
+}
+
+static void
+gtk_source_vim_im_context_finalize (GObject *object)
+{
+       GtkSourceVimIMContext *self = (GtkSourceVimIMContext *)object;
+
+       g_clear_weak_pointer (&self->view);
+       g_clear_pointer (&self->vim, gtk_source_vim_free);
+       g_clear_pointer (&self->command_text, g_free);
+       g_clear_pointer (&self->command_bar_text, g_free);
+
+       G_OBJECT_CLASS (gtk_source_vim_im_context_parent_class)->finalize (object);
+}
+
+static void
+gtk_source_vim_im_context_get_property (GObject    *object,
+                                        guint       prop_id,
+                                        GValue     *value,
+                                        GParamSpec *pspec)
+{
+       GtkSourceVimIMContext *self = GTK_SOURCE_VIM_IM_CONTEXT (object);
+
+       switch (prop_id)
+       {
+       case PROP_COMMAND_TEXT:
+               g_value_set_string (value, gtk_source_vim_im_context_get_command_text (self));
+               break;
+
+       case PROP_COMMAND_BAR_TEXT:
+               g_value_set_string (value, gtk_source_vim_im_context_get_command_bar_text (self));
+               break;
+
+       case PROP_VIEW:
+               g_value_set_object (value, gtk_source_vim_im_context_get_view (self));
+               break;
+
+       case PROP_MODE:
+               g_value_set_static_string (value, gtk_source_vim_im_context_get_mode (self));
+               break;
+
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+       }
+}
+
+static void
+gtk_source_vim_im_context_set_property (GObject      *object,
+                                        guint         prop_id,
+                                        const GValue *value,
+                                        GParamSpec   *pspec)
+{
+       switch (prop_id)
+       {
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+       }
+}
+
+static void
+gtk_source_vim_im_context_class_init (GtkSourceVimIMContextClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS (klass);
+
+       object_class->finalize = gtk_source_vim_im_context_finalize;
+       object_class->get_property = gtk_source_vim_im_context_get_property;
+       object_class->set_property = gtk_source_vim_im_context_set_property;
+
+       im_context_class->set_client_widget = gtk_source_vim_im_context_set_client_widget;
+       im_context_class->reset = gtk_source_vim_im_context_reset;
+       im_context_class->focus_in = gtk_source_vim_im_context_focus_in;
+       im_context_class->focus_out = gtk_source_vim_im_context_focus_out;
+       im_context_class->filter_keypress = gtk_source_vim_im_context_filter_keypress;
+
+       properties [PROP_VIEW] =
+               g_param_spec_object ("view",
+                                    "View",
+                                    "The view the IM context is attached to",
+                                    GTK_SOURCE_TYPE_VIEW,
+                                    (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+       properties [PROP_COMMAND_TEXT] =
+               g_param_spec_string ("command-text",
+                                    "Command Text",
+                                    "The text for the current command",
+                                    NULL,
+                                    (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+       properties [PROP_COMMAND_BAR_TEXT] =
+               g_param_spec_string ("command-bar-text",
+                                    "Command Bar Text",
+                                    "The text for the command bar",
+                                    NULL,
+                                    (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+       properties [PROP_MODE] =
+               g_param_spec_string ("mode",
+                                    "Mode",
+                                    "The VIM mode for the context",
+                                    "NORMAL",
+                                    (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gtk_source_vim_im_context_init (GtkSourceVimIMContext *self)
+{
+       static const GtkSourceVimCallbacks callbacks = {
+               .beep = gtk_source_vim_im_context_beep,
+               .set_mode = gtk_source_vim_im_context_set_mode,
+               .set_label = gtk_source_vim_im_context_set_label,
+               .filter_keypress = gtk_source_vim_im_context_propagate_keypress,
+       };
+
+       self->vim = gtk_source_vim_new (&callbacks, self);
+}
+
+/**
+ * gtk_source_vim_im_context_get_view:
+ * @self: a #GtkSourceVimIMContext
+ *
+ * Gets the #GtkSourceView that @self is attached to.
+ *
+ * Returns: (transfer none) (nullable): a #GtkSourceView or %NULL
+ *
+ * Since: 5.4
+ */
+GtkSourceView *
+gtk_source_vim_im_context_get_view (GtkSourceVimIMContext *self)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_VIM_IM_CONTEXT (self), NULL);
+
+       return self->view;
+}
+
+/**
+ * gtk_source_vim_im_context_get_command_text:
+ * @self: a #GtkSourceVimIMContext
+ *
+ * Gets the current command text as it is entered by the user.
+ *
+ * Returns: (not nullable): A string containing the command text
+ *
+ * Since: 5.4
+ */
+const char *
+gtk_source_vim_im_context_get_command_text (GtkSourceVimIMContext *self)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_VIM_IM_CONTEXT (self), NULL);
+
+       return self->command_text;
+}
+
+/**
+ * gtk_source_vim_im_context_get_command_bar_text:
+ * @self: a #GtkSourceVimIMContext
+ *
+ * Gets the current command-bar text as it is entered by the user.
+ *
+ * Returns: (not nullable): A string containing the command-bar text
+ *
+ * Since: 5.4
+ */
+const char *
+gtk_source_vim_im_context_get_command_bar_text (GtkSourceVimIMContext *self)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_VIM_IM_CONTEXT (self), NULL);
+
+       return self->command_bar_text;
+}
+
+const char *
+gtk_source_vim_im_context_get_mode (GtkSourceVimIMContext *self)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_VIM_IM_CONTEXT (self), 0);
+
+       switch ((int)self->mode)
+       {
+       case GTK_SOURCE_VIM_INSERT:
+               return "INSERT";
+       case GTK_SOURCE_VIM_REPLACE:
+               return "REPLACE";
+       case GTK_SOURCE_VIM_VIRTUAL_REPLACE:
+               return "VREPLACE";
+       case GTK_SOURCE_VIM_VISUAL:
+               return "VISUAL";
+       case GTK_SOURCE_VIM_VISUAL_LINE:
+               return "VISUAL LINE";
+       case GTK_SOURCE_VIM_VISUAL_BLOCK:
+               return "VISUAL BLOCK";
+       default:
+               return NULL;
+       }
+}
diff --git a/gtksourceview/gtksourcevimimcontext.h b/gtksourceview/gtksourcevimimcontext.h
new file mode 100644
index 00000000..e3e10470
--- /dev/null
+++ b/gtksourceview/gtksourcevimimcontext.h
@@ -0,0 +1,50 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#pragma once
+
+#if !defined (GTK_SOURCE_H_INSIDE) && !defined (GTK_SOURCE_COMPILATION)
+#error "Only <gtksourceview/gtksource.h> can be included directly."
+#endif
+
+#include <gtk/gtk.h>
+
+#include "gtksourcetypes.h"
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_VIM_IM_CONTEXT (gtk_source_vim_im_context_get_type())
+
+GTK_SOURCE_AVAILABLE_IN_5_4
+G_DECLARE_FINAL_TYPE (GtkSourceVimIMContext, gtk_source_vim_im_context, GTK_SOURCE, VIM_IM_CONTEXT, 
GtkIMContext)
+
+GTK_SOURCE_AVAILABLE_IN_5_4
+GtkIMContext     *gtk_source_vim_im_context_new                  (void);
+GTK_SOURCE_AVAILABLE_IN_5_4
+GtkSourceView    *gtk_source_vim_im_context_get_view             (GtkSourceVimIMContext *self);
+GTK_SOURCE_AVAILABLE_IN_5_4
+const char       *gtk_source_vim_im_context_get_command_text     (GtkSourceVimIMContext *self);
+GTK_SOURCE_AVAILABLE_IN_5_4
+const char       *gtk_source_vim_im_context_get_command_bar_text (GtkSourceVimIMContext *self);
+GTK_SOURCE_AVAILABLE_IN_5_4
+const char       *gtk_source_vim_im_context_get_mode             (GtkSourceVimIMContext *self);
+
+G_END_DECLS
diff --git a/gtksourceview/meson.build b/gtksourceview/meson.build
index 090f7a9c..96afb5b1 100644
--- a/gtksourceview/meson.build
+++ b/gtksourceview/meson.build
@@ -54,6 +54,7 @@ core_public_h = files([
   'gtksourcetypes.h',
   'gtksourceutils.h',
   'gtksourceview.h',
+  'gtksourcevimimcontext.h',
 ])
 
 core_public_c = files([
@@ -104,6 +105,7 @@ core_public_c = files([
   'gtksourceutils.c',
   'gtksourceversion.c',
   'gtksourceview.c',
+  'gtksourcevimimcontext.c',
 ])
 
 core_private_c = files([
@@ -133,6 +135,7 @@ core_private_c = files([
   'gtksourcesnippetbundle.c',
   'gtksourcesnippetbundle-parser.c',
   'gtksourceview-snippets.c',
+  'gtksourcevim.c',
   'implregex.c',
 ])
 
diff --git a/tests/meson.build b/tests/meson.build
index 0d3fd2c3..70e8929c 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -13,6 +13,7 @@ tests_sources = {
                        'load': ['test-load.c'],
                      'widget': ['test-widget.c'],
                     'preview': ['test-preview.c'],
+                        'vim': ['test-vim.c'],
 }
 
 tests_resources = {
diff --git a/tests/test-vim.c b/tests/test-vim.c
new file mode 100644
index 00000000..fd078e10
--- /dev/null
+++ b/tests/test-vim.c
@@ -0,0 +1,99 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <gtksourceview/gtksource.h>
+
+static gboolean
+on_close_request (GtkWindow *window,
+                  GMainLoop *main_loop)
+{
+       g_main_loop_quit (main_loop);
+       return FALSE;
+}
+
+int
+main (int argc,
+      char *argv[])
+{
+       GMainLoop *main_loop;
+       GtkWindow *window;
+       GtkSourceStyleSchemeManager *schemes;
+       GtkSourceLanguageManager *languages;
+       GtkScrolledWindow *scroller;
+       GtkSourceView *view;
+       GtkIMContext *im_context;
+       GtkEventController *key;
+       GtkSourceBuffer *buffer;
+       GtkText *command_bar;
+
+       gtk_init ();
+       gtk_source_init ();
+
+       schemes = gtk_source_style_scheme_manager_get_default ();
+       languages = gtk_source_language_manager_get_default ();
+
+       main_loop = g_main_loop_new (NULL, FALSE);
+       window = g_object_new (GTK_TYPE_WINDOW,
+                              "default-width", 800,
+                              "default-height", 600,
+                              NULL);
+       scroller = g_object_new (GTK_TYPE_SCROLLED_WINDOW, NULL);
+       buffer = gtk_source_buffer_new (NULL);
+       gtk_source_buffer_set_language (buffer, gtk_source_language_manager_get_language (languages, "c"));
+       gtk_source_buffer_set_style_scheme (buffer, gtk_source_style_scheme_manager_get_scheme (schemes, 
"Adwaita"));
+       view = g_object_new (GTK_SOURCE_TYPE_VIEW,
+                            "buffer", buffer,
+                            "monospace", TRUE,
+                            "show-line-numbers", TRUE,
+                            "top-margin", 6,
+                            "left-margin", 6,
+                            NULL);
+       command_bar = g_object_new (GTK_TYPE_TEXT,
+                                   "xalign", 0.0f,
+                                   "margin-top", 6,
+                                   "margin-bottom", 6,
+                                   NULL);
+
+       gtk_window_set_child (window, GTK_WIDGET (scroller));
+       gtk_scrolled_window_set_child (scroller, GTK_WIDGET (view));
+       gtk_text_view_set_gutter (GTK_TEXT_VIEW (view), GTK_TEXT_WINDOW_BOTTOM, GTK_WIDGET (command_bar));
+       gtk_editable_set_editable (GTK_EDITABLE (command_bar), FALSE);
+
+       im_context = gtk_source_vim_im_context_new ();
+       g_object_bind_property (im_context, "command-text", command_bar, "text", G_BINDING_SYNC_CREATE);
+       gtk_im_context_set_client_widget (im_context, GTK_WIDGET (view));
+
+       key = gtk_event_controller_key_new ();
+       gtk_event_controller_key_set_im_context (GTK_EVENT_CONTROLLER_KEY (key), im_context);
+       gtk_event_controller_set_propagation_phase (key, GTK_PHASE_CAPTURE);
+       gtk_widget_add_controller (GTK_WIDGET (view), key);
+
+       g_signal_connect (window, "close-request", G_CALLBACK (on_close_request), main_loop);
+       gtk_window_present (window);
+
+       g_main_loop_run (main_loop);
+
+       gtk_source_finalize ();
+
+       return 0;
+}


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