[gtksourceview/wip/chergert/vim] add rudimentary jumplist implementation



commit e8ed84fa7a6772498c51d0049f8b4c7e0677e77b
Author: Christian Hergert <chergert redhat com>
Date:   Thu Nov 11 16:39:00 2021 -0800

    add rudimentary jumplist implementation
    
    this does not go across files currently, and that would probably take
    additional work since you copy a jump list when creating a new frame in
    vim.

 gtksourceview/meson.build                   |   1 +
 gtksourceview/vim/gtk-source-vim-command.c  |  31 ++++
 gtksourceview/vim/gtk-source-vim-jumplist.c | 260 ++++++++++++++++++++++++++++
 gtksourceview/vim/gtk-source-vim-jumplist.h |  41 +++++
 gtksourceview/vim/gtk-source-vim-motion.c   |  10 ++
 gtksourceview/vim/gtk-source-vim-normal.c   |  11 +-
 gtksourceview/vim/gtk-source-vim-state.c    |  64 +++++++
 gtksourceview/vim/gtk-source-vim-state.h    |   6 +
 gtksourceview/vim/gtk-source-vim.c          |   4 +
 9 files changed, 427 insertions(+), 1 deletion(-)
---
diff --git a/gtksourceview/meson.build b/gtksourceview/meson.build
index c2e3e7f2..d52bf7c4 100644
--- a/gtksourceview/meson.build
+++ b/gtksourceview/meson.build
@@ -142,6 +142,7 @@ core_private_c = files([
   'vim/gtk-source-vim-command.c',
   'vim/gtk-source-vim-insert.c',
   'vim/gtk-source-vim-insert-literal.c',
+  'vim/gtk-source-vim-jumplist.c',
   'vim/gtk-source-vim-marks.c',
   'vim/gtk-source-vim-motion.c',
   'vim/gtk-source-vim-normal.c',
diff --git a/gtksourceview/vim/gtk-source-vim-command.c b/gtksourceview/vim/gtk-source-vim-command.c
index 2c865bb2..2f1f3202 100644
--- a/gtksourceview/vim/gtk-source-vim-command.c
+++ b/gtksourceview/vim/gtk-source-vim-command.c
@@ -35,6 +35,7 @@
 #include "gtk-source-vim.h"
 #include "gtk-source-vim-char-pending.h"
 #include "gtk-source-vim-command.h"
+#include "gtk-source-vim-jumplist.h"
 #include "gtk-source-vim-registers.h"
 
 typedef void (*Command) (GtkSourceVimCommand *self);
@@ -1158,6 +1159,34 @@ gtk_source_vim_command_repeat (GtkSourceVimState *state)
        gtk_text_buffer_delete_mark (GTK_TEXT_BUFFER (buffer), mark);
 }
 
+static void
+gtk_source_vim_command_jump_backward (GtkSourceVimCommand *self)
+{
+       GtkTextIter iter;
+
+       g_assert (GTK_SOURCE_IS_VIM_COMMAND (self));
+
+       if (gtk_source_vim_state_jump_backward (GTK_SOURCE_VIM_STATE (self), &iter))
+       {
+               gtk_source_vim_state_select (GTK_SOURCE_VIM_STATE (self), &iter, &iter);
+               self->ignore_mark = TRUE;
+       }
+}
+
+static void
+gtk_source_vim_command_jump_forward (GtkSourceVimCommand *self)
+{
+       GtkTextIter iter;
+
+       g_assert (GTK_SOURCE_IS_VIM_COMMAND (self));
+
+       if (gtk_source_vim_state_jump_forward (GTK_SOURCE_VIM_STATE (self), &iter))
+       {
+               gtk_source_vim_state_select (GTK_SOURCE_VIM_STATE (self), &iter, &iter);
+               self->ignore_mark = TRUE;
+       }
+}
+
 static void
 gtk_source_vim_command_enter (GtkSourceVimState *state)
 {
@@ -1379,6 +1408,8 @@ gtk_source_vim_command_class_init (GtkSourceVimCommandClass *klass)
        ADD_COMMAND ("search",         gtk_source_vim_command_search);
        ADD_COMMAND ("search-replace", gtk_source_vim_command_search_replace);
        ADD_COMMAND ("search-reverse", gtk_source_vim_command_search_reverse);
+       ADD_COMMAND ("jump-backward",  gtk_source_vim_command_jump_backward);
+       ADD_COMMAND ("jump-forward",   gtk_source_vim_command_jump_forward);
 #undef ADD_COMMAND
 
        g_ptr_array_sort (commands_sorted, sort_longest_first);
diff --git a/gtksourceview/vim/gtk-source-vim-jumplist.c b/gtksourceview/vim/gtk-source-vim-jumplist.c
new file mode 100644
index 00000000..0a7bf861
--- /dev/null
+++ b/gtksourceview/vim/gtk-source-vim-jumplist.c
@@ -0,0 +1,260 @@
+/*
+ * 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 "gtk-source-vim-jumplist.h"
+
+#define MAX_JUMPS 100
+
+typedef struct
+{
+       GList        link;
+       GtkTextMark *mark;
+} Jump;
+
+struct _GtkSourceVimJumplist
+{
+       GtkSourceVimState parent_instance;
+       GQueue back;
+       GQueue forward;
+};
+
+G_DEFINE_TYPE (GtkSourceVimJumplist, gtk_source_vim_jumplist, GTK_SOURCE_TYPE_VIM_STATE)
+
+GtkSourceVimState *
+gtk_source_vim_jumplist_new (void)
+{
+       return g_object_new (GTK_SOURCE_TYPE_VIM_JUMPLIST, NULL);
+}
+
+static void
+jump_free (Jump *j)
+{
+       g_assert (j->link.data == j);
+       g_assert (j->link.prev == NULL);
+       g_assert (j->link.next == NULL);
+
+       j->link.data = NULL;
+
+       if (j->mark != NULL)
+       {
+               GtkTextBuffer *buffer = gtk_text_mark_get_buffer (j->mark);
+               gtk_text_buffer_delete_mark (buffer, j->mark);
+               g_object_unref (j->mark);
+               j->mark = NULL;
+       }
+
+       g_slice_free (Jump, j);
+}
+
+static gboolean
+jump_equal (const Jump *a,
+            const Jump *b)
+{
+       GtkTextIter ai, bi;
+
+       g_assert (GTK_IS_TEXT_MARK (a->mark));
+       g_assert (GTK_IS_TEXT_MARK (b->mark));
+
+       if (a == b)
+               return TRUE;
+
+       if (a->mark == b->mark)
+               return TRUE;
+
+       gtk_text_buffer_get_iter_at_mark (gtk_text_mark_get_buffer (a->mark), &ai, a->mark);
+       gtk_text_buffer_get_iter_at_mark (gtk_text_mark_get_buffer (b->mark), &bi, b->mark);
+
+       if (gtk_text_iter_get_line (&ai) == gtk_text_iter_get_line (&bi))
+               return TRUE;
+
+       return FALSE;
+}
+
+static void
+clear_queue (GQueue *q)
+{
+       while (q->length > 0)
+       {
+               Jump *head = q->head->data;
+               g_queue_unlink (q, &head->link);
+               jump_free (head);
+       }
+}
+
+static void
+gtk_source_vim_jumplist_dispose (GObject *object)
+{
+       GtkSourceVimJumplist *self = (GtkSourceVimJumplist *)object;
+
+       clear_queue (&self->back);
+       clear_queue (&self->forward);
+
+       G_OBJECT_CLASS (gtk_source_vim_jumplist_parent_class)->dispose (object);
+}
+
+static void
+gtk_source_vim_jumplist_class_init (GtkSourceVimJumplistClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->dispose = gtk_source_vim_jumplist_dispose;
+}
+
+static void
+gtk_source_vim_jumplist_init (GtkSourceVimJumplist *self)
+{
+}
+
+void
+gtk_source_vim_jumplist_push (GtkSourceVimJumplist *self,
+                              const GtkTextIter    *iter)
+{
+       GtkTextBuffer *buffer;
+       Jump *j;
+
+       g_return_if_fail (GTK_SOURCE_IS_VIM_JUMPLIST (self));
+       g_return_if_fail (iter != NULL);
+
+       buffer = gtk_text_iter_get_buffer (iter);
+
+       j = g_slice_new0 (Jump);
+       j->link.data = j;
+       j->mark = g_object_ref (gtk_text_buffer_create_mark (buffer, NULL, iter, TRUE));
+
+       g_assert (GTK_IS_TEXT_MARK (j->mark));
+
+       for (const GList *item = self->back.tail; item; item = item->prev)
+       {
+               Jump *j2 = item->data;
+
+               if (jump_equal (j, j2))
+               {
+                       g_queue_unlink (&self->back, &j2->link);
+                       jump_free (j2);
+                       goto push;
+               }
+       }
+
+       for (const GList *item = self->forward.head; item; item = item->next)
+       {
+               Jump *j2 = item->data;
+
+               if (jump_equal (j, j2))
+               {
+                       g_queue_unlink (&self->forward, &j2->link);
+                       jump_free (j2);
+                       goto push;
+               }
+       }
+
+push:
+       if (self->back.length + self->forward.length >= MAX_JUMPS)
+       {
+               if (self->back.length > 0)
+               {
+                       Jump *head = self->back.head->data;
+                       g_queue_unlink (&self->back, &head->link);
+                       jump_free (head);
+               }
+               else
+               {
+                       Jump *tail = self->forward.tail->data;
+                       g_queue_unlink (&self->forward, &tail->link);
+                       jump_free (tail);
+               }
+       }
+
+       g_queue_push_tail_link (&self->back, &j->link);
+}
+
+gboolean
+gtk_source_vim_jumplist_previous (GtkSourceVimJumplist *self,
+                                  GtkTextIter          *iter)
+{
+       GtkSourceBuffer *buffer;
+       GtkTextIter before;
+       Jump current = {0};
+       gboolean ret = FALSE;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_VIM_JUMPLIST (self), FALSE);
+       g_return_val_if_fail (iter != NULL, FALSE);
+
+       buffer = gtk_source_vim_state_get_buffer (GTK_SOURCE_VIM_STATE (self), &before, NULL);
+
+       current.mark = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (buffer));
+       current.link.data = &current;
+
+       gtk_source_vim_jumplist_push (self, &before);
+
+       while (!ret && self->back.length > 0)
+       {
+               Jump *j = g_queue_peek_tail (&self->back);
+
+               if (!jump_equal (&current, j))
+               {
+                       gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer), iter, j->mark);
+                       ret = TRUE;
+               }
+
+               g_queue_unlink (&self->back, &j->link);
+               g_queue_push_head_link (&self->forward, &j->link);
+       }
+
+       return ret;
+}
+
+gboolean
+gtk_source_vim_jumplist_next (GtkSourceVimJumplist *self,
+                              GtkTextIter          *iter)
+{
+       GtkSourceBuffer *buffer;
+       GtkTextIter before;
+       Jump current = {0};
+       gboolean ret = FALSE;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_VIM_JUMPLIST (self), FALSE);
+       g_return_val_if_fail (iter != NULL, FALSE);
+
+       buffer = gtk_source_vim_state_get_buffer (GTK_SOURCE_VIM_STATE (self), &before, NULL);
+
+       current.mark = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (buffer));
+       current.link.data = &current;
+
+       gtk_source_vim_jumplist_push (self, &before);
+
+       while (!ret && self->forward.length > 0)
+       {
+               Jump *j = g_queue_peek_head (&self->forward);
+
+               if (!jump_equal (&current, j))
+               {
+                       gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer), iter, j->mark);
+                       ret = TRUE;
+               }
+
+               g_queue_unlink (&self->forward, &j->link);
+               g_queue_push_tail_link (&self->back, &j->link);
+       }
+
+       return ret;
+}
diff --git a/gtksourceview/vim/gtk-source-vim-jumplist.h b/gtksourceview/vim/gtk-source-vim-jumplist.h
new file mode 100644
index 00000000..f35b892a
--- /dev/null
+++ b/gtksourceview/vim/gtk-source-vim-jumplist.h
@@ -0,0 +1,41 @@
+/*
+ * 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 "gtk-source-vim-state.h"
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_VIM_JUMPLIST (gtk_source_vim_jumplist_get_type())
+
+G_DECLARE_FINAL_TYPE (GtkSourceVimJumplist, gtk_source_vim_jumplist, GTK_SOURCE, VIM_JUMPLIST, 
GtkSourceVimState)
+
+GtkSourceVimState *gtk_source_vim_jumplist_new      (void);
+void               gtk_source_vim_jumplist_push     (GtkSourceVimJumplist *self,
+                                                     const GtkTextIter    *iter);
+gboolean           gtk_source_vim_jumplist_previous (GtkSourceVimJumplist *self,
+                                                     GtkTextIter          *iter);
+gboolean           gtk_source_vim_jumplist_next     (GtkSourceVimJumplist *self,
+                                                     GtkTextIter          *iter);
+
+
+G_END_DECLS
diff --git a/gtksourceview/vim/gtk-source-vim-motion.c b/gtksourceview/vim/gtk-source-vim-motion.c
index 10993433..a58eb3ee 100644
--- a/gtksourceview/vim/gtk-source-vim-motion.c
+++ b/gtksourceview/vim/gtk-source-vim-motion.c
@@ -1622,6 +1622,7 @@ gtk_source_vim_motion_handle_keypress (GtkSourceVimState *state,
                switch (keyval)
                {
                        case GDK_KEY_g:
+                               self->is_jump = TRUE;
                                return gtk_source_vim_motion_complete (self, motion_buffer_start_first_char, 
INCLUSIVE, LINEWISE);
 
                        case GDK_KEY_e:
@@ -1955,6 +1956,15 @@ gtk_source_vim_motion_leave (GtkSourceVimState *state)
 
        if (self->apply_on_leave)
        {
+               /* If this motion is a jump, then add it to the jumplist */
+               if (self->is_jump)
+               {
+                       GtkTextIter origin;
+
+                       gtk_source_vim_state_get_buffer (state, &origin, NULL);
+                       gtk_source_vim_state_push_jump (state, &origin);
+               }
+
                gtk_source_vim_motion_repeat (state);
        }
 }
diff --git a/gtksourceview/vim/gtk-source-vim-normal.c b/gtksourceview/vim/gtk-source-vim-normal.c
index bc02fcfe..c51a2328 100644
--- a/gtksourceview/vim/gtk-source-vim-normal.c
+++ b/gtksourceview/vim/gtk-source-vim-normal.c
@@ -1126,6 +1126,14 @@ key_handler_initial (GtkSourceVimNormal *self,
                                self->handler = key_handler_command;
                                break;
 
+                       case GDK_KEY_o:
+                               gtk_source_vim_normal_begin_command (self, NULL, NULL, "jump-backward", 0);
+                               return TRUE;
+
+                       case GDK_KEY_i:
+                               gtk_source_vim_normal_begin_command (self, NULL, NULL, "jump-forward", 0);
+                               return TRUE;
+
                        default:
                                break;
                }
@@ -1326,12 +1334,13 @@ gtk_source_vim_normal_resume (GtkSourceVimState *state,
        GtkSourceBuffer *buffer;
        GtkSourceView *view;
        GtkTextMark *insert;
+       GtkTextIter origin;
        gboolean unparent = TRUE;
 
        g_assert (GTK_SOURCE_IS_VIM_NORMAL (self));
        g_assert (GTK_SOURCE_IS_VIM_STATE (from));
 
-       buffer = gtk_source_vim_state_get_buffer (state, NULL, NULL);
+       buffer = gtk_source_vim_state_get_buffer (state, &origin, NULL);
        insert = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (buffer));
        view = gtk_source_vim_state_get_view (state);
 
diff --git a/gtksourceview/vim/gtk-source-vim-state.c b/gtksourceview/vim/gtk-source-vim-state.c
index d73fcfa4..0063d737 100644
--- a/gtksourceview/vim/gtk-source-vim-state.c
+++ b/gtksourceview/vim/gtk-source-vim-state.c
@@ -29,6 +29,7 @@
 #include "gtksourceutils-private.h"
 #include "gtksourceview.h"
 
+#include "gtk-source-vim-jumplist.h"
 #include "gtk-source-vim-registers.h"
 #include "gtk-source-vim-marks.h"
 #include "gtk-source-vim-state.h"
@@ -38,6 +39,7 @@ typedef struct
        /* Owned reference to marks/registers (usually set low in the stack) */
        GtkSourceVimState *registers;
        GtkSourceVimState *marks;
+       GtkSourceVimState *jumplist;
 
        /* Owned reference to the view (usually set low in the stack) */
        GtkSourceView *view;
@@ -289,6 +291,7 @@ gtk_source_vim_state_dispose (GObject *object)
        g_clear_weak_pointer (&priv->view);
        gtk_source_vim_state_release (&priv->registers);
        gtk_source_vim_state_release (&priv->marks);
+       gtk_source_vim_state_release (&priv->jumplist);
 
        /* First remove the children from our list */
        while (priv->children.length > 0)
@@ -1386,3 +1389,64 @@ gtk_source_vim_state_get_iter_at_mark (GtkSourceVimState *self,
 
        return gtk_source_vim_marks_get_iter (marks, name, iter);
 }
+
+static GtkSourceVimJumplist *
+gtk_source_vim_state_get_jumplist (GtkSourceVimState *self)
+{
+       GtkSourceVimState *root;
+       GtkSourceVimStatePrivate *priv;
+
+       g_assert (GTK_SOURCE_IS_VIM_STATE (self));
+
+       root = gtk_source_vim_state_get_root (self);
+       priv = gtk_source_vim_state_get_instance_private (root);
+
+       if (priv->jumplist == NULL)
+       {
+               priv->jumplist = gtk_source_vim_jumplist_new ();
+               gtk_source_vim_state_set_parent (priv->jumplist, root);
+       }
+
+       return GTK_SOURCE_VIM_JUMPLIST (priv->jumplist);
+}
+
+void
+gtk_source_vim_state_push_jump (GtkSourceVimState *self,
+                                const GtkTextIter *iter)
+{
+       GtkSourceVimJumplist *jumplist;
+
+       g_return_if_fail (GTK_SOURCE_IS_VIM_STATE (self));
+       g_return_if_fail (iter != NULL);
+
+       jumplist = gtk_source_vim_state_get_jumplist (self);
+       gtk_source_vim_jumplist_push (jumplist, iter);
+}
+
+gboolean
+gtk_source_vim_state_jump_backward (GtkSourceVimState *self,
+                                    GtkTextIter       *iter)
+{
+       GtkSourceVimJumplist *jumplist;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_VIM_STATE (self), FALSE);
+       g_return_val_if_fail (iter != NULL, FALSE);
+
+       jumplist = gtk_source_vim_state_get_jumplist (self);
+
+       return gtk_source_vim_jumplist_previous (jumplist, iter);
+}
+
+gboolean
+gtk_source_vim_state_jump_forward (GtkSourceVimState *self,
+                                   GtkTextIter       *iter)
+{
+       GtkSourceVimJumplist *jumplist;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_VIM_STATE (self), FALSE);
+       g_return_val_if_fail (iter != NULL, FALSE);
+
+       jumplist = gtk_source_vim_state_get_jumplist (self);
+
+       return gtk_source_vim_jumplist_next (jumplist, iter);
+}
diff --git a/gtksourceview/vim/gtk-source-vim-state.h b/gtksourceview/vim/gtk-source-vim-state.h
index 3ecce7d9..d8bf8405 100644
--- a/gtksourceview/vim/gtk-source-vim-state.h
+++ b/gtksourceview/vim/gtk-source-vim-state.h
@@ -138,6 +138,12 @@ void               gtk_source_vim_state_keyval_to_string           (guint
 void               gtk_source_vim_state_keyval_unescaped           (guint                     keyval,
                                                                     GdkModifierType           mods,
                                                                     char                      string[16]);
+void               gtk_source_vim_state_push_jump                  (GtkSourceVimState        *self,
+                                                                    const GtkTextIter        *iter);
+gboolean           gtk_source_vim_state_jump_backward              (GtkSourceVimState        *self,
+                                                                    GtkTextIter              *iter);
+gboolean           gtk_source_vim_state_jump_forward               (GtkSourceVimState        *self,
+                                                                    GtkTextIter              *iter);
 
 static inline void
 gtk_source_vim_state_release (GtkSourceVimState **dest)
diff --git a/gtksourceview/vim/gtk-source-vim.c b/gtksourceview/vim/gtk-source-vim.c
index ad1cdc0c..0ea22bf6 100644
--- a/gtksourceview/vim/gtk-source-vim.c
+++ b/gtksourceview/vim/gtk-source-vim.c
@@ -204,11 +204,13 @@ gtk_source_vim_view_set (GtkSourceVimState *state)
 {
        GtkSourceVim *self = (GtkSourceVim *)state;
        GtkSourceView *view;
+       GtkTextIter iter;
 
        g_assert (GTK_SOURCE_IS_VIM (self));
        g_assert (gtk_source_vim_state_get_child (state) == NULL);
 
        view = gtk_source_vim_state_get_view (state);
+       gtk_source_vim_state_get_buffer (state, &iter, NULL);
 
        g_signal_connect_object (view,
                                 "notify::buffer",
@@ -217,6 +219,8 @@ gtk_source_vim_view_set (GtkSourceVimState *state)
                                 G_CONNECT_SWAPPED);
        on_notify_buffer_cb (self, NULL, view);
 
+       gtk_source_vim_state_push_jump (state, &iter);
+
        gtk_source_vim_state_push (state, gtk_source_vim_normal_new ());
 }
 


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