[gtksourceview/wip/chergert/vim] start on motion state



commit 66a5ad649e0617e7506d1a4bc121ef8e16b1c719
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]