[gtksourceview/wip/chergert/vim: 39/363] start on motion state
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtksourceview/wip/chergert/vim: 39/363] start on motion state
- Date: Mon, 8 Nov 2021 19:53:45 +0000 (UTC)
commit 3a7e72c81d2e3b0bf62a40b25e483ce33a0d74f4
Author: Christian Hergert <chergert redhat com>
Date: Fri Oct 22 17:06:24 2021 -0700
start on motion state
gtksourceview/vim/gtk-source-vim-motion.c | 465 ++++++++++++++++++++++++++++++
gtksourceview/vim/gtk-source-vim-motion.h | 17 ++
gtksourceview/vim/gtk-source-vim-normal.c | 51 +++-
gtksourceview/vim/meson.build | 1 +
4 files changed, 521 insertions(+), 13 deletions(-)
---
diff --git a/gtksourceview/vim/gtk-source-vim-motion.c b/gtksourceview/vim/gtk-source-vim-motion.c
new file mode 100644
index 00000000..e5f033c7
--- /dev/null
+++ b/gtksourceview/vim/gtk-source-vim-motion.c
@@ -0,0 +1,465 @@
+/*
+ * 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 "gtk-source-vim-motion.h"
+
+typedef gboolean (*Motion) (GtkTextIter *iter,
+ GtkSourceView *view);
+
+struct _GtkSourceVimMotion
+{
+ GtkSourceVimState parent_instance;
+ Motion motion;
+ int number;
+ guint failed : 1;
+};
+
+G_DEFINE_TYPE (GtkSourceVimMotion, gtk_source_vim_motion, GTK_SOURCE_TYPE_VIM_STATE)
+
+static void
+get_iter_at_visual_column (GtkSourceView *view,
+ GtkTextIter *iter,
+ guint column)
+{
+ gunichar tab_char;
+ guint visual_col = 0;
+ guint tab_width;
+
+ g_assert (GTK_SOURCE_IS_VIEW (view));
+ g_assert (iter != NULL);
+
+ tab_char = g_utf8_get_char ("\t");
+ tab_width = gtk_source_view_get_tab_width (view);
+ gtk_text_iter_set_line_offset (iter, 0);
+
+ while (!gtk_text_iter_ends_line (iter))
+ {
+ if (gtk_text_iter_get_char (iter) == tab_char)
+ visual_col += (tab_width - (visual_col % tab_width));
+ else
+ ++visual_col;
+
+ if (visual_col > column)
+ break;
+
+ /* This does not handle invisible text correctly, but
+ * gtk_text_iter_forward_visible_cursor_position is too slow.
+ */
+ if (!gtk_text_iter_forward_char (iter))
+ break;
+ }
+}
+
+static gboolean
+motion_line_start (GtkTextIter *iter,
+ GtkSourceView *view)
+{
+ if (!gtk_text_iter_starts_line (iter))
+ {
+ gtk_text_iter_set_line_offset (iter, 0);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+motion_line_first_char (GtkTextIter *iter,
+ GtkSourceView *view)
+{
+ if (!gtk_text_iter_starts_line (iter))
+ {
+ gtk_text_iter_set_line_offset (iter, 0);
+ }
+
+ while (!gtk_text_iter_ends_line (iter) &&
+ g_unichar_isspace (gtk_text_iter_get_char (iter)))
+ {
+ if (!gtk_text_iter_forward_char (iter))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+motion_forward_char (GtkTextIter *iter,
+ GtkSourceView *view)
+{
+ return gtk_text_iter_forward_char (iter);
+}
+
+static gboolean
+motion_forward_char_same_line (GtkTextIter *iter,
+ GtkSourceView *view)
+{
+ if (!gtk_text_iter_ends_line (iter))
+ {
+ return gtk_text_iter_forward_char (iter);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+motion_backward_char (GtkTextIter *iter,
+ GtkSourceView *view)
+{
+ return gtk_text_iter_backward_char (iter);
+}
+
+static gboolean
+motion_backward_char_same_line (GtkTextIter *iter,
+ GtkSourceView *view)
+{
+ if (!gtk_text_iter_starts_line (iter))
+ {
+ return gtk_text_iter_backward_char (iter);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+motion_prev_line (GtkTextIter *iter,
+ GtkSourceView *view)
+{
+ guint line = gtk_text_iter_get_line (iter);
+
+ if (line > 0)
+ return gtk_text_buffer_get_iter_at_line (gtk_text_iter_get_buffer (iter), iter, line - 1);
+
+ if (!gtk_text_iter_is_start (iter))
+ {
+ gtk_text_iter_set_line_offset (iter, 0);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+motion_next_line (GtkTextIter *iter,
+ GtkSourceView *view)
+{
+ if (!gtk_text_iter_ends_line (iter))
+ {
+ gtk_text_iter_forward_to_line_end (iter);
+ }
+
+ return gtk_text_iter_forward_char (iter);
+}
+
+static gboolean
+motion_next_line_first_char (GtkTextIter *iter,
+ GtkSourceView *view)
+{
+ return motion_next_line (iter, view) &&
+ motion_line_first_char (iter, view);
+}
+
+static gboolean
+motion_next_line_visual_column (GtkTextIter *iter,
+ GtkSourceView *view)
+{
+ guint column;
+
+ /* TODO: We need a way to persist the visual column across
+ * multiple motion movements so that we don't break when
+ * we come across an empty column.
+ */
+ column = gtk_source_view_get_visual_column (view, iter);
+
+ if (!motion_next_line (iter, view))
+ return FALSE;
+
+ get_iter_at_visual_column (view, iter, column);
+
+ return TRUE;
+}
+
+static gboolean
+motion_prev_line_visual_column (GtkTextIter *iter,
+ GtkSourceView *view)
+{
+ guint column;
+
+ /* TODO: We need a way to persist the visual column across
+ * multiple motion movements so that we don't break when
+ * we come across an empty column.
+ */
+ column = gtk_source_view_get_visual_column (view, iter);
+
+ if (!motion_prev_line (iter, view))
+ return FALSE;
+
+ get_iter_at_visual_column (view, iter, column);
+
+ return TRUE;
+}
+
+static gboolean
+motion_line_end (GtkTextIter *iter,
+ GtkSourceView *view)
+{
+ return gtk_text_iter_forward_to_line_end (iter);
+}
+
+static gboolean
+motion_last_line_first_char (GtkTextIter *iter,
+ GtkSourceView *view)
+{
+ gtk_text_buffer_get_end_iter (gtk_text_iter_get_buffer (iter), iter);
+ gtk_text_iter_set_line_offset (iter, 0);
+ while (!gtk_text_iter_is_end (iter) &&
+ g_unichar_isspace (gtk_text_iter_get_char (iter)))
+ gtk_text_iter_forward_char (iter);
+ return TRUE;
+}
+
+static gboolean
+motion_screen_top (GtkTextIter *iter,
+ GtkSourceView *view)
+{
+ GdkRectangle rect;
+
+ gtk_text_view_get_visible_rect (GTK_TEXT_VIEW (view), &rect);
+ gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), iter, rect.x, rect.y);
+
+ return TRUE;
+}
+
+static gboolean
+motion_screen_bottom (GtkTextIter *iter,
+ GtkSourceView *view)
+{
+ GdkRectangle rect;
+
+ gtk_text_view_get_visible_rect (GTK_TEXT_VIEW (view), &rect);
+ gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), iter, rect.x, rect.y + rect.height);
+
+ return TRUE;
+}
+
+static gboolean
+motion_screen_middle (GtkTextIter *iter,
+ GtkSourceView *view)
+{
+ GdkRectangle rect;
+
+ gtk_text_view_get_visible_rect (GTK_TEXT_VIEW (view), &rect);
+ gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), iter, rect.x, rect.y + rect.height / 2);
+
+ return TRUE;
+}
+
+GtkSourceVimState *
+gtk_source_vim_motion_new (void)
+{
+ return g_object_new (GTK_SOURCE_TYPE_VIM_MOTION, NULL);
+}
+
+static gboolean
+gtk_source_vim_motion_bail (GtkSourceVimMotion *self)
+{
+ g_assert (GTK_SOURCE_IS_VIM_MOTION (self));
+
+ self->failed = TRUE;
+ gtk_source_vim_state_pop (GTK_SOURCE_VIM_STATE (self));
+
+ return TRUE;
+}
+
+static gboolean
+gtk_source_vim_motion_complete (GtkSourceVimMotion *self,
+ Motion motion)
+{
+ g_assert (GTK_SOURCE_IS_VIM_MOTION (self));
+
+ self->motion = motion;
+ gtk_source_vim_state_pop (GTK_SOURCE_VIM_STATE (self));
+ return TRUE;
+}
+
+static gboolean
+gtk_source_vim_motion_handle_keypress (GtkSourceVimState *state,
+ guint keyval,
+ guint keycode,
+ GdkModifierType mods,
+ const char *string)
+{
+ GtkSourceVimMotion *self = (GtkSourceVimMotion *)state;
+
+ g_assert (GTK_SOURCE_IS_VIM_MOTION (self));
+
+ if (self->number != 0)
+ {
+ if (keyval >= GDK_KEY_0 && keyval <= GDK_KEY_9)
+ {
+ self->number = self->number * 10 + (keyval - GDK_KEY_0);
+ return TRUE;
+ }
+ }
+
+ switch (keyval)
+ {
+ case GDK_KEY_0:
+ case GDK_KEY_KP_0:
+ case GDK_KEY_Home:
+ case GDK_KEY_bar:
+ return gtk_source_vim_motion_complete (self, motion_line_start);
+
+ case GDK_KEY_1: case GDK_KEY_KP_1:
+ case GDK_KEY_2: case GDK_KEY_KP_2:
+ case GDK_KEY_3: case GDK_KEY_KP_3:
+ case GDK_KEY_4: case GDK_KEY_KP_4:
+ case GDK_KEY_5: case GDK_KEY_KP_5:
+ case GDK_KEY_6: case GDK_KEY_KP_6:
+ case GDK_KEY_7: case GDK_KEY_KP_7:
+ case GDK_KEY_8: case GDK_KEY_KP_8:
+ case GDK_KEY_9: case GDK_KEY_KP_9:
+ if (keyval >= GDK_KEY_0 && keyval <= GDK_KEY_9)
+ self->number = keyval - GDK_KEY_0;
+ else
+ self->number = keyval - GDK_KEY_KP_0;
+ return TRUE;
+
+ case GDK_KEY_asciicircum:
+ case GDK_KEY_underscore:
+ return gtk_source_vim_motion_complete (self, motion_line_first_char);
+
+ case GDK_KEY_space:
+ return gtk_source_vim_motion_complete (self, motion_forward_char);
+
+ case GDK_KEY_BackSpace:
+ return gtk_source_vim_motion_complete (self, motion_backward_char);
+
+ case GDK_KEY_Left:
+ case GDK_KEY_h:
+ return gtk_source_vim_motion_complete (self, motion_backward_char_same_line);
+
+ case GDK_KEY_Right:
+ case GDK_KEY_l:
+ return gtk_source_vim_motion_complete (self, motion_forward_char_same_line);
+
+ case GDK_KEY_ISO_Enter:
+ case GDK_KEY_KP_Enter:
+ case GDK_KEY_Return:
+ return gtk_source_vim_motion_complete (self, motion_next_line_first_char);
+
+ case GDK_KEY_End:
+ case GDK_KEY_dollar:
+ return gtk_source_vim_motion_complete (self, motion_line_end);
+
+ case GDK_KEY_Down:
+ case GDK_KEY_j:
+ return gtk_source_vim_motion_complete (self, motion_next_line_visual_column);
+
+ case GDK_KEY_Up:
+ case GDK_KEY_k:
+ return gtk_source_vim_motion_complete (self, motion_prev_line_visual_column);
+
+ case GDK_KEY_G:
+ return gtk_source_vim_motion_complete (self, motion_last_line_first_char);
+
+ case GDK_KEY_H:
+ return gtk_source_vim_motion_complete (self, motion_screen_top);
+
+ case GDK_KEY_M:
+ return gtk_source_vim_motion_complete (self, motion_screen_middle);
+
+ case GDK_KEY_L:
+ return gtk_source_vim_motion_complete (self, motion_screen_bottom);
+
+ case GDK_KEY_b:
+ case GDK_KEY_B:
+ case GDK_KEY_e:
+ case GDK_KEY_E:
+ case GDK_KEY_f:
+ case GDK_KEY_F:
+ case GDK_KEY_n:
+ case GDK_KEY_N:
+ case GDK_KEY_parenleft:
+ case GDK_KEY_parenright:
+ case GDK_KEY_percent:
+ case GDK_KEY_w:
+ case GDK_KEY_W:
+ /* TODO */
+ G_GNUC_FALLTHROUGH;
+
+ default:
+ return gtk_source_vim_motion_bail (self);
+ }
+
+ return FALSE;
+}
+
+static void
+gtk_source_vim_motion_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (gtk_source_vim_motion_parent_class)->finalize (object);
+}
+
+static void
+gtk_source_vim_motion_class_init (GtkSourceVimMotionClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkSourceVimStateClass *state_class = GTK_SOURCE_VIM_STATE_CLASS (klass);
+
+ object_class->finalize = gtk_source_vim_motion_finalize;
+
+ state_class->handle_keypress = gtk_source_vim_motion_handle_keypress;
+}
+
+static void
+gtk_source_vim_motion_init (GtkSourceVimMotion *self)
+{
+}
+
+gboolean
+gtk_source_vim_motion_apply (GtkSourceVimMotion *self,
+ GtkTextIter *iter)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_VIM_MOTION (self), FALSE);
+
+ if (self->motion == NULL || self->failed)
+ {
+ return FALSE;
+ }
+
+ do
+ {
+ GtkSourceView *view = gtk_source_vim_state_get_view (GTK_SOURCE_VIM_STATE (self));
+
+ if (!self->motion (iter, view))
+ {
+ return FALSE;
+ }
+ } while (--self->number > 1);
+
+ return TRUE;
+}
diff --git a/gtksourceview/vim/gtk-source-vim-motion.h b/gtksourceview/vim/gtk-source-vim-motion.h
index b9bb4b8c..8dc5baa5 100644
--- a/gtksourceview/vim/gtk-source-vim-motion.h
+++ b/gtksourceview/vim/gtk-source-vim-motion.h
@@ -19,3 +19,20 @@
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "gtk-source-vim-state.h"
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_VIM_MOTION (gtk_source_vim_motion_get_type())
+
+G_DECLARE_FINAL_TYPE (GtkSourceVimMotion, gtk_source_vim_motion, GTK_SOURCE, VIM_MOTION, GtkSourceVimState)
+
+GtkSourceVimState *gtk_source_vim_motion_new (void);
+gboolean gtk_source_vim_motion_apply (GtkSourceVimMotion *self,
+ GtkTextIter *iter);
+
+G_END_DECLS
diff --git a/gtksourceview/vim/gtk-source-vim-normal.c b/gtksourceview/vim/gtk-source-vim-normal.c
index 45a7174e..d8c3b79f 100644
--- a/gtksourceview/vim/gtk-source-vim-normal.c
+++ b/gtksourceview/vim/gtk-source-vim-normal.c
@@ -23,6 +23,7 @@
#include "gtk-source-vim-command-bar.h"
#include "gtk-source-vim-insert.h"
+#include "gtk-source-vim-motion.h"
#include "gtk-source-vim-normal.h"
#include "gtk-source-vim-replace.h"
@@ -62,18 +63,6 @@ gtk_source_vim_normal_bail (GtkSourceVimNormal *self)
return TRUE;
}
-static gboolean
-key_handler_motion (GtkSourceVimNormal *self,
- guint keyval,
- guint keycode,
- GdkModifierType mods,
- const char *string)
-{
- g_assert (GTK_SOURCE_IS_VIM_NORMAL (self));
-
- return TRUE;
-}
-
static gboolean
key_handler_repeat (GtkSourceVimNormal *self,
guint keyval,
@@ -305,6 +294,24 @@ key_handler_g (GtkSourceVimNormal *self,
return TRUE;
}
+static gboolean
+key_handler_motion (GtkSourceVimNormal *self,
+ guint keyval,
+ guint keycode,
+ GdkModifierType mods,
+ const char *string)
+{
+ GtkSourceVimState *new_state;
+
+ g_assert (GTK_SOURCE_IS_VIM_NORMAL (self));
+
+ new_state = gtk_source_vim_motion_new ();
+ gtk_source_vim_state_push (GTK_SOURCE_VIM_STATE (self), new_state);
+ GTK_SOURCE_VIM_STATE_GET_CLASS (new_state)->handle_keypress (new_state, keyval, keycode, mods,
string);
+
+ return TRUE;
+}
+
static gboolean
key_handler_initial (GtkSourceVimNormal *self,
guint keyval,
@@ -372,7 +379,8 @@ key_handler_initial (GtkSourceVimNormal *self,
{
switch (keyval)
{
- case GDK_KEY_0: case GDK_KEY_KP_0:
+ case GDK_KEY_0:
+ case GDK_KEY_KP_0:
case GDK_KEY_asciicircum:
case GDK_KEY_b:
case GDK_KEY_bar:
@@ -553,6 +561,23 @@ gtk_source_vim_normal_restore (GtkSourceVimState *state,
&insert, &insert);
}
}
+ else if (GTK_SOURCE_IS_VIM_MOTION (from))
+ {
+ GtkSourceBuffer *buffer;
+ GtkTextIter insert;
+
+ buffer = gtk_source_vim_state_get_buffer (state, &insert, NULL);
+
+ if (gtk_source_vim_motion_apply (GTK_SOURCE_VIM_MOTION (from), &insert))
+ {
+ gtk_text_buffer_select_range (GTK_TEXT_BUFFER (buffer), &insert, &insert);
+ }
+
+ /* TODO: - keep cursor at end of line.
+ * - possibly use with command.
+ * etc..
+ */
+ }
gtk_source_vim_state_set_overwrite (state, TRUE);
}
diff --git a/gtksourceview/vim/meson.build b/gtksourceview/vim/meson.build
index 0ea488d5..48486881 100644
--- a/gtksourceview/vim/meson.build
+++ b/gtksourceview/vim/meson.build
@@ -1,6 +1,7 @@
vim_sources = files([
'gtk-source-vim.c',
'gtk-source-vim-command-bar.c',
+ 'gtk-source-vim-motion.c',
'gtk-source-vim-normal.c',
'gtk-source-vim-insert.c',
'gtk-source-vim-insert-literal.c',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]