[gtksourceview/wip/chergert/vim: 20/21] wip on vim input method
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtksourceview/wip/chergert/vim: 20/21] wip on vim input method
- Date: Tue, 19 Oct 2021 23:13:13 +0000 (UTC)
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]