[gtksourceview/wip/chergert/vim] more work on text objects, with tests this time



commit b5c2da17a027f2954fabccd471661028c45aff1e
Author: Christian Hergert <chergert redhat com>
Date:   Thu Nov 4 17:51:52 2021 -0700

    more work on text objects, with tests this time

 gtksourceview/meson.build                      |  17 +-
 gtksourceview/vim/gtk-source-vim-motion.c      |  45 +++++-
 gtksourceview/vim/gtk-source-vim-normal.c      |  71 +++++++--
 gtksourceview/vim/gtk-source-vim-state.h       |   2 +-
 gtksourceview/vim/gtk-source-vim-text-object.c | 212 +++++++++++++++++--------
 gtksourceview/vim/meson.build                  |  16 --
 testsuite/meson.build                          |   1 +
 testsuite/test-vim-text-object.c               | 145 +++++++++++++++++
 8 files changed, 401 insertions(+), 108 deletions(-)
---
diff --git a/gtksourceview/meson.build b/gtksourceview/meson.build
index 278a0168..553f17eb 100644
--- a/gtksourceview/meson.build
+++ b/gtksourceview/meson.build
@@ -5,8 +5,6 @@ core_marshallers = gnome.genmarshal('gtksource-marshal',
   valist_marshallers: true,
 )
 
-subdir('vim')
-
 core_public_h = files([
   'gtksource.h',
   'gtksourcebuffer.h',
@@ -138,6 +136,20 @@ core_private_c = files([
   'gtksourcesnippetbundle-parser.c',
   'gtksourceview-snippets.c',
   'implregex.c',
+  'vim/gtk-source-vim.c',
+  'vim/gtk-source-vim-char-pending.c',
+  'vim/gtk-source-vim-command-bar.c',
+  'vim/gtk-source-vim-command.c',
+  'vim/gtk-source-vim-insert.c',
+  'vim/gtk-source-vim-insert-literal.c',
+  'vim/gtk-source-vim-motion.c',
+  'vim/gtk-source-vim-normal.c',
+  'vim/gtk-source-vim-registers.c',
+  'vim/gtk-source-vim-replace.c',
+  'vim/gtk-source-vim-state.c',
+  'vim/gtk-source-vim-text-history.c',
+  'vim/gtk-source-vim-text-object.c',
+  'vim/gtk-source-vim-visual.c',
 ])
 
 core_c_args = [
@@ -225,7 +237,6 @@ core_sources = [
   gtksourceversion_h,
   core_marshallers,
   gtksource_res,
-  vim_sources,
 ]
 
 install_headers(
diff --git a/gtksourceview/vim/gtk-source-vim-motion.c b/gtksourceview/vim/gtk-source-vim-motion.c
index c985ad8c..19d8f571 100644
--- a/gtksourceview/vim/gtk-source-vim-motion.c
+++ b/gtksourceview/vim/gtk-source-vim-motion.c
@@ -1997,25 +1997,29 @@ gtk_source_vim_motion_set_linewise_keyval (GtkSourceVimMotion *self,
 gboolean
 gtk_source_vim_iter_forward_word_end (GtkTextIter *iter)
 {
-       return forward_classified_end (iter, classify_word_newline_stop);
+       forward_classified_end (iter, classify_word_newline_stop);
+       return TRUE;
 }
 
 gboolean
 gtk_source_vim_iter_forward_WORD_end (GtkTextIter *iter)
 {
-       return forward_classified_end (iter, classify_WORD_newline_stop);
+       forward_classified_end (iter, classify_WORD_newline_stop);
+       return TRUE;
 }
 
 gboolean
 gtk_source_vim_iter_backward_word_start (GtkTextIter *iter)
 {
-       return backward_classified_start (iter, classify_word_newline_stop);
+       backward_classified_start (iter, classify_word_newline_stop);
+       return TRUE;
 }
 
 gboolean
 gtk_source_vim_iter_backward_WORD_start (GtkTextIter *iter)
 {
-       return backward_classified_start (iter, classify_WORD_newline_stop);
+       backward_classified_start (iter, classify_WORD_newline_stop);
+       return TRUE;
 }
 
 static inline gboolean
@@ -2137,6 +2141,9 @@ static gboolean
 gtk_source_vim_iter_backward_block_start (GtkTextIter *iter,
                                           gunichar     ch)
 {
+       if (gtk_text_iter_get_char (iter) == ch)
+               return TRUE;
+
        return gtk_text_iter_backward_find_char (iter, find_predicate, GSIZE_TO_POINTER (ch), NULL);
 }
 
@@ -2144,6 +2151,9 @@ static gboolean
 gtk_source_vim_iter_forward_block_end (GtkTextIter *iter,
                                        gunichar     ch)
 {
+       if (gtk_text_iter_get_char (iter) == ch)
+               return TRUE;
+
        return gtk_text_iter_forward_find_char (iter, find_predicate, GSIZE_TO_POINTER (ch), NULL);
 }
 
@@ -2200,7 +2210,7 @@ gtk_source_vim_iter_backward_quote_start (GtkTextIter *iter,
                                           gunichar     ch)
 {
        GtkTextIter limit = *iter;
-       gtk_text_iter_set_line_offset (iter, 0);
+       gtk_text_iter_set_line_offset (&limit, 0);
        return gtk_text_iter_backward_find_char (iter, find_predicate, GSIZE_TO_POINTER (ch), &limit);
 }
 
@@ -2208,9 +2218,32 @@ static gboolean
 gtk_source_vim_iter_forward_quote_end (GtkTextIter *iter,
                                        gunichar     ch)
 {
-       GtkTextIter limit = *iter;
+       GtkTextIter limit;
+
+       /* The way this seems to work in VIM is that we match
+        * to backwards if we have a preference, then forwards.
+        * So if we find @ch before us and we are currently on
+        * @ch, then that is preferred over scanning forward.
+        */
+       if (ch == gtk_text_iter_get_char (iter) &&
+           !gtk_text_iter_is_start (iter))
+       {
+               GtkTextIter alt = *iter;
+
+               gtk_text_iter_backward_char (&alt);
+
+               if (gtk_source_vim_iter_backward_quote_start (&alt, ch))
+               {
+                       return TRUE;
+               }
+
+               gtk_text_iter_forward_char (iter);
+       }
+
+       limit = *iter;
        if (!gtk_text_iter_ends_line (&limit))
                gtk_text_iter_forward_to_line_end (&limit);
+
        return gtk_text_iter_forward_find_char (iter, find_predicate, GSIZE_TO_POINTER (ch), &limit);
 }
 
diff --git a/gtksourceview/vim/gtk-source-vim-normal.c b/gtksourceview/vim/gtk-source-vim-normal.c
index b7387c17..c27047fd 100644
--- a/gtksourceview/vim/gtk-source-vim-normal.c
+++ b/gtksourceview/vim/gtk-source-vim-normal.c
@@ -41,6 +41,13 @@ typedef gboolean (*KeyHandler) (GtkSourceVimNormal *self,
                                 GdkModifierType     mods,
                                 const char         *string);
 
+typedef enum
+{
+       CHANGE_NONE  = 0,
+       CHANGE_INNER = 1,
+       CHANGE_A     = 2,
+} ChangeModifier;
+
 struct _GtkSourceVimNormal
 {
        GtkSourceVimState  parent_instance;
@@ -49,6 +56,7 @@ struct _GtkSourceVimNormal
        GtkSourceVimState *last_visual;
        KeyHandler         handler;
        int                count;
+       ChangeModifier     change_modifier;
        guint              has_count : 1;
 };
 
@@ -634,25 +642,51 @@ key_handler_viewport (GtkSourceVimNormal *self,
 }
 
 static gboolean
-key_handler_ci (GtkSourceVimNormal *self,
-                guint               keyval,
-                guint               keycode,
-                GdkModifierType     mods,
-                const char         *string)
+key_handler_c_with_modifier (GtkSourceVimNormal *self,
+                             guint               keyval,
+                             guint               keycode,
+                             GdkModifierType     mods,
+                             const char         *string)
 {
        g_assert (GTK_SOURCE_IS_VIM_NORMAL (self));
 
-       return gtk_source_vim_normal_bail (self);
-}
+       /* This should be a TextObject and we might need a "motion or
+        * text object" state.
+        */
 
-static gboolean
-key_handler_ca (GtkSourceVimNormal *self,
-                guint               keyval,
-                guint               keycode,
-                GdkModifierType     mods,
-                const char         *string)
-{
-       g_assert (GTK_SOURCE_IS_VIM_NORMAL (self));
+       switch (keyval)
+       {
+               case GDK_KEY_w:
+               case GDK_KEY_W:
+               case GDK_KEY_p:
+               case GDK_KEY_s:
+                       break;
+
+               case GDK_KEY_bracketleft:
+               case GDK_KEY_bracketright:
+                       break;
+
+               case GDK_KEY_braceleft:
+               case GDK_KEY_braceright:
+               case GDK_KEY_B:
+                       break;
+
+               case GDK_KEY_less:
+               case GDK_KEY_greater:
+                       break;
+
+               case GDK_KEY_apostrophe:
+               case GDK_KEY_quotedbl:
+                       break;
+
+               case GDK_KEY_parenleft:
+               case GDK_KEY_parenright:
+               case GDK_KEY_b:
+                       break;
+
+               default:
+                       break;
+       }
 
        return gtk_source_vim_normal_bail (self);
 }
@@ -677,11 +711,13 @@ key_handler_c (GtkSourceVimNormal *self,
                        return TRUE;
 
                case GDK_KEY_i:
-                       self->handler = key_handler_ci;
+                       self->change_modifier = CHANGE_INNER;
+                       self->handler = key_handler_c_with_modifier;
                        return TRUE;
 
                case GDK_KEY_a:
-                       self->handler = key_handler_ca;
+                       self->change_modifier = CHANGE_A;
+                       self->handler = key_handler_c_with_modifier;
                        return TRUE;
 
                default:
@@ -1205,6 +1241,7 @@ gtk_source_vim_normal_clear (GtkSourceVimNormal *self)
        self->handler = key_handler_initial;
        self->count = 0;
        self->has_count = FALSE;
+       self->change_modifier = CHANGE_NONE;
 
        g_string_truncate (self->command_text, 0);
 
diff --git a/gtksourceview/vim/gtk-source-vim-state.h b/gtksourceview/vim/gtk-source-vim-state.h
index 51014eaf..5ab5889c 100644
--- a/gtksourceview/vim/gtk-source-vim-state.h
+++ b/gtksourceview/vim/gtk-source-vim-state.h
@@ -23,7 +23,7 @@
 
 #include <gtk/gtk.h>
 
-#include "gtksourcetypes.h"
+#include <gtksourceview/gtksourcetypes.h>
 
 G_BEGIN_DECLS
 
diff --git a/gtksourceview/vim/gtk-source-vim-text-object.c b/gtksourceview/vim/gtk-source-vim-text-object.c
index 4c45c0ad..73d99c5c 100644
--- a/gtksourceview/vim/gtk-source-vim-text-object.c
+++ b/gtksourceview/vim/gtk-source-vim-text-object.c
@@ -24,16 +24,16 @@
 #include "gtk-source-vim-motion.h"
 #include "gtk-source-vim-text-object.h"
 
-typedef gboolean (*TextObjectMotion) (GtkTextIter *iter);
+typedef gboolean (*TextObjectMotion) (GtkTextIter       *iter);
+typedef void     (*TextObjectExtend) (const GtkTextIter *origin,
+                                      GtkTextIter       *inner_begin,
+                                      GtkTextIter       *inner_end,
+                                      GtkTextIter       *a_begin,
+                                      GtkTextIter       *a_end);
 
 enum {
-       TEXT_OBJECT_INNER = 0,
-       TEXT_OBJECT_A = 1,
-};
-
-enum {
-       TEXT_OBJECT_LINE_ENDS = 0,
-       TEXT_OBJECT_BUFFER_ENDS = 1,
+       TEXT_OBJECT_INNER,
+       TEXT_OBJECT_A,
 };
 
 struct _GtkSourceVimTextObject
@@ -41,62 +41,143 @@ struct _GtkSourceVimTextObject
        GtkSourceVimState parent_instance;
        TextObjectMotion  forward_end;
        TextObjectMotion  backward_start;
+       TextObjectExtend  extend;
        guint             inner_or_a : 1;
-       guint             captivity : 1;
 };
 
 G_DEFINE_TYPE (GtkSourceVimTextObject, gtk_source_vim_text_object, GTK_SOURCE_TYPE_VIM_STATE)
 
+static inline gboolean
+iter_isspace (const GtkTextIter *iter)
+{
+       return g_unichar_isspace (gtk_text_iter_get_char (iter));
+}
+
+static void
+backward_to_first_space (GtkTextIter *iter)
+{
+       while (!gtk_text_iter_starts_line (iter))
+       {
+               gtk_text_iter_backward_char (iter);
+
+               if (!iter_isspace (iter))
+               {
+                       gtk_text_iter_forward_char (iter);
+                       return;
+               }
+       }
+}
+
+static void
+text_object_extend_word (const GtkTextIter *origin,
+                         GtkTextIter       *inner_begin,
+                         GtkTextIter       *inner_end,
+                         GtkTextIter       *a_begin,
+                         GtkTextIter       *a_end)
+{
+       gtk_text_iter_forward_char (inner_end);
+
+       *a_begin = *inner_begin;
+       *a_end = *inner_end;
+
+       while (!gtk_text_iter_ends_line (a_end) && iter_isspace (a_end))
+               gtk_text_iter_forward_char (a_end);
+
+       backward_to_first_space (a_begin);
+
+       /* If @origin is between a_begin and inner_begin, then we
+        * actually want to just select that space.
+        */
+       if (gtk_text_iter_compare (a_begin, origin) <= 0 &&
+           gtk_text_iter_compare (origin, inner_begin) < 0)
+       {
+               *inner_end = *inner_begin;
+               *inner_begin = *a_begin;
+               *a_end = *inner_end;
+       }
+}
+
+static void
+text_object_extend_one (const GtkTextIter *origin,
+                        GtkTextIter       *inner_begin,
+                        GtkTextIter       *inner_end,
+                        GtkTextIter       *a_begin,
+                        GtkTextIter       *a_end)
+{
+       *a_begin = *inner_begin;
+       gtk_text_iter_forward_char (inner_begin);
+       *a_end = *inner_end;
+       gtk_text_iter_forward_char (a_end);
+}
+
+static void
+text_object_extend_paragraph (const GtkTextIter *origin,
+                              GtkTextIter       *inner_begin,
+                              GtkTextIter       *inner_end,
+                              GtkTextIter       *a_begin,
+                              GtkTextIter       *a_end)
+{
+}
+
+static void
+text_object_extend_sentence (const GtkTextIter *origin,
+                             GtkTextIter       *inner_begin,
+                             GtkTextIter       *inner_end,
+                             GtkTextIter       *a_begin,
+                             GtkTextIter       *a_end)
+{
+}
+
 static GtkSourceVimState *
 gtk_source_vim_text_object_new (TextObjectMotion forward_end,
                                 TextObjectMotion backward_start,
-                                guint            inner_or_a,
-                                guint            captivity)
+                                TextObjectExtend extend,
+                                guint            inner_or_a)
 {
        GtkSourceVimTextObject *self;
 
        self = g_object_new (GTK_SOURCE_TYPE_VIM_TEXT_OBJECT, NULL);
        self->forward_end = forward_end;
        self->backward_start = backward_start;
-       self->inner_or_a = !!inner_or_a;
-       self->captivity = !!captivity;
+       self->extend = extend;
+       self->inner_or_a = inner_or_a;
 
        return GTK_SOURCE_VIM_STATE (self);
 }
 
-#define TEXT_OBJECT_CTOR(name, forward, backward, inner_or_a, captivity)        \
+#define TEXT_OBJECT_CTOR(name, forward, backward, extend, inner_or_a)           \
 GtkSourceVimState *                                                             \
 gtk_source_vim_text_object_new_##name (void)                                    \
 {                                                                               \
        return gtk_source_vim_text_object_new (gtk_source_vim_iter_##forward,   \
-                                              gtk_source_vim_iter_##backward,  \
-                                              TEXT_OBJECT_##inner_or_a,        \
-                                              TEXT_OBJECT_##captivity);        \
+                                              gtk_source_vim_iter_##backward,  \
+                                              text_object_extend_##extend,     \
+                                              TEXT_OBJECT_##inner_or_a);       \
 }
 
-TEXT_OBJECT_CTOR (inner_word, forward_word_end, backward_word_start, INNER, LINE_ENDS)
-TEXT_OBJECT_CTOR (inner_WORD, forward_WORD_end, backward_WORD_start, INNER, LINE_ENDS)
-TEXT_OBJECT_CTOR (inner_sentence, forward_sentence_end, backward_word_start, INNER, BUFFER_ENDS)
-TEXT_OBJECT_CTOR (inner_paragraph, forward_paragraph_end, backward_paragraph_start, INNER, BUFFER_ENDS)
-TEXT_OBJECT_CTOR (inner_block_paren, forward_block_paren_end, backward_block_paren_start, INNER, BUFFER_ENDS)
-TEXT_OBJECT_CTOR (inner_block_brace, forward_block_brace_end, backward_block_brace_start, INNER, BUFFER_ENDS)
-TEXT_OBJECT_CTOR (inner_block_bracket, forward_block_bracket_end, backward_block_bracket_start, INNER, 
BUFFER_ENDS)
-TEXT_OBJECT_CTOR (inner_block_lt_gt, forward_block_lt_gt_end, backward_block_lt_gt_start, INNER, BUFFER_ENDS)
-TEXT_OBJECT_CTOR (inner_quote_double, forward_quote_double, backward_quote_double, INNER, LINE_ENDS)
-TEXT_OBJECT_CTOR (inner_quote_single, forward_quote_single, backward_quote_single, INNER, LINE_ENDS)
-TEXT_OBJECT_CTOR (inner_quote_grave, forward_quote_grave, backward_quote_grave, INNER, LINE_ENDS)
-
-TEXT_OBJECT_CTOR (a_word, forward_word_end, backward_word_start, A, LINE_ENDS)
-TEXT_OBJECT_CTOR (a_WORD, forward_WORD_end, backward_WORD_start, A, LINE_ENDS)
-TEXT_OBJECT_CTOR (a_sentence, forward_sentence_end, backward_word_start, A, BUFFER_ENDS)
-TEXT_OBJECT_CTOR (a_paragraph, forward_paragraph_end, backward_paragraph_start, A, BUFFER_ENDS)
-TEXT_OBJECT_CTOR (a_block_paren, forward_block_paren_end, backward_block_paren_start, A, BUFFER_ENDS)
-TEXT_OBJECT_CTOR (a_block_brace, forward_block_brace_end, backward_block_brace_start, A, BUFFER_ENDS)
-TEXT_OBJECT_CTOR (a_block_bracket, forward_block_bracket_end, backward_block_bracket_start, A, BUFFER_ENDS)
-TEXT_OBJECT_CTOR (a_block_lt_gt, forward_block_lt_gt_end, backward_block_lt_gt_start, A, BUFFER_ENDS)
-TEXT_OBJECT_CTOR (a_quote_double, forward_quote_double, backward_quote_double, A, LINE_ENDS)
-TEXT_OBJECT_CTOR (a_quote_single, forward_quote_single, backward_quote_single, A, LINE_ENDS)
-TEXT_OBJECT_CTOR (a_quote_grave, forward_quote_grave, backward_quote_grave, A, LINE_ENDS)
+TEXT_OBJECT_CTOR (inner_word, forward_word_end, backward_word_start, word, INNER);
+TEXT_OBJECT_CTOR (inner_WORD, forward_WORD_end, backward_WORD_start, word, INNER);
+TEXT_OBJECT_CTOR (inner_sentence, forward_sentence_end, backward_sentence_start, sentence, INNER);
+TEXT_OBJECT_CTOR (inner_paragraph, forward_paragraph_end, backward_paragraph_start, paragraph, INNER);
+TEXT_OBJECT_CTOR (inner_block_paren, forward_block_paren_end, backward_block_paren_start, one, INNER);
+TEXT_OBJECT_CTOR (inner_block_brace, forward_block_brace_end, backward_block_brace_start, one, INNER);
+TEXT_OBJECT_CTOR (inner_block_bracket, forward_block_bracket_end, backward_block_bracket_start, one, INNER);
+TEXT_OBJECT_CTOR (inner_block_lt_gt, forward_block_lt_gt_end, backward_block_lt_gt_start, one, INNER);
+TEXT_OBJECT_CTOR (inner_quote_double, forward_quote_double, backward_quote_double, one, INNER);
+TEXT_OBJECT_CTOR (inner_quote_single, forward_quote_single, backward_quote_single, one, INNER);
+TEXT_OBJECT_CTOR (inner_quote_grave, forward_quote_grave, backward_quote_grave, one, INNER);
+
+TEXT_OBJECT_CTOR (a_word, forward_word_end, backward_word_start, word, A);
+TEXT_OBJECT_CTOR (a_WORD, forward_WORD_end, backward_WORD_start, word, A);
+TEXT_OBJECT_CTOR (a_sentence, forward_sentence_end, backward_sentence_start, sentence, A);
+TEXT_OBJECT_CTOR (a_paragraph, forward_paragraph_end, backward_paragraph_start, paragraph, A);
+TEXT_OBJECT_CTOR (a_block_paren, forward_block_paren_end, backward_block_paren_start, one, A);
+TEXT_OBJECT_CTOR (a_block_brace, forward_block_brace_end, backward_block_brace_start, one, A);
+TEXT_OBJECT_CTOR (a_block_bracket, forward_block_bracket_end, backward_block_bracket_start, one, A);
+TEXT_OBJECT_CTOR (a_block_lt_gt, forward_block_lt_gt_end, backward_block_lt_gt_start, one, A);
+TEXT_OBJECT_CTOR (a_quote_double, forward_quote_double, backward_quote_double, one, A);
+TEXT_OBJECT_CTOR (a_quote_single, forward_quote_single, backward_quote_single, one, A);
+TEXT_OBJECT_CTOR (a_quote_grave, forward_quote_grave, backward_quote_grave, one, A);
 
 #undef TEXT_OBJECT_CTOR
 
@@ -124,47 +205,48 @@ gtk_source_vim_text_object_select (GtkSourceVimTextObject *self,
                                    GtkTextIter            *begin,
                                    GtkTextIter            *end)
 {
-       GtkTextIter b, e;
+       GtkTextIter inner_begin;
+       GtkTextIter inner_end;
+       GtkTextIter a_begin;
+       GtkTextIter a_end;
 
        g_return_val_if_fail (GTK_SOURCE_IS_VIM_TEXT_OBJECT (self), FALSE);
        g_return_val_if_fail (begin != NULL, FALSE);
        g_return_val_if_fail (end != NULL, FALSE);
        g_return_val_if_fail (GTK_IS_TEXT_BUFFER (gtk_text_iter_get_buffer (begin)), FALSE);
+       g_return_val_if_fail (self->forward_end != NULL, FALSE);
+       g_return_val_if_fail (self->backward_start != NULL, FALSE);
+       g_return_val_if_fail (self->extend != NULL, FALSE);
 
-       if (self->forward_end == NULL || self->backward_start == NULL)
+       inner_end = *begin;
+       if (!self->forward_end (&inner_end))
+               {
+               g_print ("forward end failed\n");
                return FALSE;
+               }
 
-       b = e = *begin;
-
-       if (!self->forward_end (&e))
+       inner_begin = inner_end;
+       if (!self->backward_start (&inner_begin))
+               {
+               g_print ("backward start failed\n");
                return FALSE;
+               }
 
-       b = e;
+       a_begin = inner_begin;
+       a_end = inner_end;
 
-       if (!self->backward_start (&b))
-               return FALSE;
+       self->extend (begin, &inner_begin, &inner_end, &a_begin, &a_end);
 
-       if (self->captivity == TEXT_OBJECT_LINE_ENDS)
+       if (self->inner_or_a == TEXT_OBJECT_INNER)
        {
-               if (gtk_text_iter_get_line (&b) != gtk_text_iter_get_line (&e) ||
-                   gtk_text_iter_get_line (&b) != gtk_text_iter_get_line (begin) ||
-                   gtk_text_iter_get_line (&e) != gtk_text_iter_get_line (begin))
-               {
-                       return FALSE;
-               }
+               *begin = inner_begin;
+               *end = inner_end;
        }
-
-       /* If we're INNER and find the new begin position being after
-        * where we started, then we want to select the space between
-        * the new beginning and the previous ending.
-        */
-       if (self->inner_or_a == TEXT_OBJECT_INNER &&
-           gtk_text_iter_compare (begin, &b) < 0)
+       else
        {
+               *begin = a_begin;
+               *end = a_end;
        }
 
-       *begin = b;
-       *end = e;
-
        return TRUE;
 }
diff --git a/testsuite/meson.build b/testsuite/meson.build
index 4bd0f6ea..97ea1911 100644
--- a/testsuite/meson.build
+++ b/testsuite/meson.build
@@ -37,6 +37,7 @@ testsuite_sources = [
   ['test-syntax'],
   ['test-utils'],
   ['test-view'],
+  ['test-vim-text-object'],
 ]
 
 foreach test: testsuite_sources
diff --git a/testsuite/test-vim-text-object.c b/testsuite/test-vim-text-object.c
new file mode 100644
index 00000000..e9f585a8
--- /dev/null
+++ b/testsuite/test-vim-text-object.c
@@ -0,0 +1,145 @@
+/*
+ * 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>
+#include <gtksourceview/vim/gtk-source-vim-text-object.h>
+
+static void
+run_test (GtkSourceVimState *text_object,
+          const char        *text,
+         guint              position,
+          const char        *expect_selection,
+          gboolean           expect_fail)
+{
+       GtkSourceBuffer *buffer;
+       GtkTextIter begin, end;
+
+       g_assert (GTK_SOURCE_IS_VIM_TEXT_OBJECT (text_object));
+       g_assert (text != NULL);
+
+       buffer = gtk_source_buffer_new (NULL);
+       gtk_text_buffer_set_text (GTK_TEXT_BUFFER (buffer), text, -1);
+
+       gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (buffer), &begin, position);
+       end = begin;
+
+       if (!gtk_source_vim_text_object_select (GTK_SOURCE_VIM_TEXT_OBJECT (text_object), &begin, &end))
+       {
+               if (expect_fail)
+                       goto cleanup;
+               g_error ("Failed to select text-object\n\n'''%s'''\npotiion: %u\nepxecting: '''%s'''",
+                        text, position, expect_selection);
+       }
+
+       if (expect_fail)
+       {
+               char *out = gtk_text_iter_get_slice (&begin, &end);
+               g_error ("Expected to fail selection but got '''%s'''", out);
+               g_free (out);
+       }
+       else
+       {
+               char *selected_text = gtk_text_iter_get_slice (&begin, &end);
+               g_assert_cmpstr (selected_text, ==, expect_selection);
+               g_free (selected_text);
+       }
+
+cleanup:
+       g_clear_object (&buffer);
+       g_clear_object (&text_object);
+}
+
+static void
+test_word (void)
+{
+       run_test (gtk_source_vim_text_object_new_inner_word (), "", 0, "", FALSE);
+       run_test (gtk_source_vim_text_object_new_inner_word (), "this is some- text to modify\n", 8, "some", 
FALSE);
+       run_test (gtk_source_vim_text_object_new_inner_word (), "something  here\n", 10, "  ", FALSE);
+       run_test (gtk_source_vim_text_object_new_inner_word (), "something  here", 9, "  ", FALSE);
+       run_test (gtk_source_vim_text_object_new_inner_word (), "a", 0, "a", FALSE);
+       run_test (gtk_source_vim_text_object_new_inner_word (), "a b", 1, " ", FALSE);
+       //run_test (gtk_source_vim_text_object_new_a_word (), "a b", 1, " b", FALSE);
+}
+
+static void
+test_WORD (void)
+{
+       run_test (gtk_source_vim_text_object_new_inner_WORD (),
+                 "this is some- text to modify\n", 8, "some-", FALSE);
+       run_test (gtk_source_vim_text_object_new_inner_WORD (),
+                 "something  here\n", 10, "  ", FALSE);
+       run_test (gtk_source_vim_text_object_new_inner_WORD (),
+                 "something  here", 9, "  ", FALSE);
+}
+
+static void
+test_block (void)
+{
+       run_test (gtk_source_vim_text_object_new_a_block_paren (),
+                 "this_is_a_function (some stuff\n  and some more)\ntrailing",
+                 23, "(some stuff\n  and some more)", FALSE);
+       run_test (gtk_source_vim_text_object_new_inner_block_paren (),
+                 "this_is_a_function (some stuff\n  and some more)\ntrailing",
+                 23, "some stuff\n  and some more", FALSE);
+       run_test (gtk_source_vim_text_object_new_inner_block_paren (),
+                 "(should not match\n", 5, NULL, TRUE);
+
+       run_test (gtk_source_vim_text_object_new_inner_block_paren (), "(m)", 0, "m", FALSE);
+       run_test (gtk_source_vim_text_object_new_inner_block_paren (), "(m)", 1, "m", FALSE);
+       run_test (gtk_source_vim_text_object_new_inner_block_paren (), "(m)", 2, "m", FALSE);
+       run_test (gtk_source_vim_text_object_new_inner_block_paren (), "(m)", 3, NULL, TRUE);
+       run_test (gtk_source_vim_text_object_new_a_block_paren (), "(m)", 0, "(m)", FALSE);
+       run_test (gtk_source_vim_text_object_new_a_block_paren (), "(m)", 1, "(m)", FALSE);
+       run_test (gtk_source_vim_text_object_new_a_block_paren (), "(m)", 2, "(m)", FALSE);
+       run_test (gtk_source_vim_text_object_new_inner_block_paren (), "(m)", 3, NULL, TRUE);
+}
+
+static void
+test_quote (void)
+{
+       run_test (gtk_source_vim_text_object_new_inner_quote_double (), "\"this is a string.\"", 0, "this is 
a string.", FALSE);
+       run_test (gtk_source_vim_text_object_new_a_quote_double (), "\"this is a string.\"", 0, "\"this is a 
string.\"", FALSE);
+       run_test (gtk_source_vim_text_object_new_inner_quote_double (), "\"this is a string.\n", 0, NULL, 
TRUE);
+       run_test (gtk_source_vim_text_object_new_inner_quote_double (), "\"this \"is a string.\"", 6, "this 
", FALSE);
+       run_test (gtk_source_vim_text_object_new_a_quote_double (), "\"this \"is a string.\"", 6, "\"this 
\"", FALSE);
+       run_test (gtk_source_vim_text_object_new_inner_quote_double (), "\"this \"is a string.\"", 7, "is a 
string.", FALSE);
+       run_test (gtk_source_vim_text_object_new_inner_quote_double (), "\"this \"is a string.", 7, NULL, 
TRUE);
+}
+
+int
+main (int argc,
+      char *argv[])
+{
+       int ret;
+
+       gtk_init ();
+       gtk_source_init ();
+       g_test_init (&argc, &argv, NULL);
+       g_test_add_func ("/GtkSourceView/vim-text-object/word", test_word);
+       g_test_add_func ("/GtkSourceView/vim-text-object/WORD", test_WORD);
+       g_test_add_func ("/GtkSourceView/vim-text-object/block", test_block);
+       g_test_add_func ("/GtkSourceView/vim-text-object/quote", test_quote);
+       ret = g_test_run ();
+       gtk_source_finalize ();
+       return ret;
+}


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