[gtksourceview/wip/chergert/snippets] start moving snippets mechanics outside of gtksourceview.c



commit 3ee35a04c0fa3e485119ff0444997fe164c53e10
Author: Christian Hergert <chergert redhat com>
Date:   Tue Jan 21 19:33:10 2020 -0800

    start moving snippets mechanics outside of gtksourceview.c

 gtksourceview/gtksourcesnippet-private.h |   4 +-
 gtksourceview/gtksourcesnippet.c         |   4 +-
 gtksourceview/gtksourceview-private.h    |  50 ++++
 gtksourceview/gtksourceview-snippets.c   | 477 +++++++++++++++++++++++++++++++
 gtksourceview/gtksourceview.c            | 340 +---------------------
 gtksourceview/gtksourceview.h            |   2 -
 gtksourceview/meson.build                |   1 +
 7 files changed, 545 insertions(+), 333 deletions(-)
---
diff --git a/gtksourceview/gtksourcesnippet-private.h b/gtksourceview/gtksourcesnippet-private.h
index 19f3981e..6338514a 100644
--- a/gtksourceview/gtksourcesnippet-private.h
+++ b/gtksourceview/gtksourcesnippet-private.h
@@ -46,13 +46,13 @@ G_GNUC_INTERNAL
 void         _gtk_source_snippet_before_insert_text         (GtkSourceSnippet *self,
                                                              GtkTextBuffer    *buffer,
                                                              GtkTextIter      *iter,
-                                                             gchar            *text,
+                                                             const gchar      *text,
                                                              gint              len);
 G_GNUC_INTERNAL
 void         _gtk_source_snippet_after_insert_text          (GtkSourceSnippet *self,
                                                              GtkTextBuffer    *buffer,
                                                              GtkTextIter      *iter,
-                                                             gchar            *text,
+                                                             const gchar      *text,
                                                              gint              len);
 G_GNUC_INTERNAL
 void         _gtk_source_snippet_before_delete_range        (GtkSourceSnippet *self,
diff --git a/gtksourceview/gtksourcesnippet.c b/gtksourceview/gtksourcesnippet.c
index 14edd854..3f87235d 100644
--- a/gtksourceview/gtksourcesnippet.c
+++ b/gtksourceview/gtksourcesnippet.c
@@ -863,7 +863,7 @@ void
 _gtk_source_snippet_before_insert_text (GtkSourceSnippet *self,
                                         GtkTextBuffer    *buffer,
                                         GtkTextIter      *iter,
-                                        gchar            *text,
+                                        const gchar      *text,
                                         gint              len)
 {
        gint utf8_len;
@@ -890,7 +890,7 @@ void
 _gtk_source_snippet_after_insert_text (GtkSourceSnippet *self,
                                        GtkTextBuffer    *buffer,
                                        GtkTextIter      *iter,
-                                       gchar            *text,
+                                       const gchar      *text,
                                        gint              len)
 {
        GtkSourceSnippetChunk *chunk;
diff --git a/gtksourceview/gtksourceview-private.h b/gtksourceview/gtksourceview-private.h
new file mode 100644
index 00000000..920ea02e
--- /dev/null
+++ b/gtksourceview/gtksourceview-private.h
@@ -0,0 +1,50 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 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/>.
+ */
+
+#pragma once
+
+#include "gtksourceview.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GtkSourceViewSnippets
+{
+       GtkSourceView *view;
+       GtkTextBuffer *buffer;
+       GQueue         queue;
+       gulong         view_notify_buffer_handler;
+       gulong         buffer_insert_text_handler;
+       gulong         buffer_insert_text_after_handler;
+       gulong         buffer_delete_range_handler;
+       gulong         buffer_delete_range_after_handler;
+} GtkSourceViewSnippets;
+
+void     _gtk_source_view_snippets_init        (GtkSourceViewSnippets *snippets,
+                                                GtkSourceView         *view);
+void     _gtk_source_view_snippets_shutdown    (GtkSourceViewSnippets *snippets);
+void     _gtk_source_view_snippets_push        (GtkSourceViewSnippets *snippets,
+                                                GtkSourceSnippet      *snippet,
+                                                GtkTextIter           *iter);
+void     _gtk_source_view_snippets_pop         (GtkSourceViewSnippets *snippets);
+gboolean _gtk_source_view_snippets_key_pressed (GtkSourceViewSnippets *snippets,
+                                                guint                  key,
+                                                guint                  keycode,
+                                                GdkModifierType        state);
+
+G_END_DECLS
diff --git a/gtksourceview/gtksourceview-snippets.c b/gtksourceview/gtksourceview-snippets.c
new file mode 100644
index 00000000..e2dd6975
--- /dev/null
+++ b/gtksourceview/gtksourceview-snippets.c
@@ -0,0 +1,477 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 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/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "gtksourceiter-private.h"
+#include "gtksourcesnippet-private.h"
+#include "gtksourcesnippetchunk.h"
+#include "gtksourceview-private.h"
+
+static void
+gtk_source_view_snippets_block (GtkSourceViewSnippets *snippets)
+{
+       g_assert (snippets != NULL);
+
+       g_signal_handler_block (snippets->buffer,
+                               snippets->buffer_insert_text_handler);
+       g_signal_handler_block (snippets->buffer,
+                               snippets->buffer_insert_text_after_handler);
+       g_signal_handler_block (snippets->buffer,
+                               snippets->buffer_delete_range_handler);
+       g_signal_handler_block (snippets->buffer,
+                               snippets->buffer_delete_range_after_handler);
+}
+
+static void
+gtk_source_view_snippets_unblock (GtkSourceViewSnippets *snippets)
+{
+       g_assert (snippets != NULL);
+
+       g_signal_handler_unblock (snippets->buffer,
+                                 snippets->buffer_insert_text_handler);
+       g_signal_handler_unblock (snippets->buffer,
+                                 snippets->buffer_insert_text_after_handler);
+       g_signal_handler_unblock (snippets->buffer,
+                                 snippets->buffer_delete_range_handler);
+       g_signal_handler_unblock (snippets->buffer,
+                                 snippets->buffer_delete_range_after_handler);
+}
+
+static void
+buffer_insert_text_cb (GtkTextBuffer         *buffer,
+                       GtkTextIter           *location,
+                       const gchar           *text,
+                       gint                   len,
+                       GtkSourceViewSnippets *snippets)
+{
+       GtkSourceSnippet *snippet;
+
+       g_assert (GTK_IS_TEXT_BUFFER (buffer));
+       g_assert (location != NULL);
+       g_assert (text != NULL);
+       g_assert (snippets != NULL);
+
+       snippet = g_queue_peek_head (&snippets->queue);
+
+       if (snippet != NULL)
+       {
+               /* We'll complete the user action in the after phase */
+               gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer));
+
+               gtk_source_view_snippets_block (snippets);
+               _gtk_source_snippet_before_insert_text (snippet,
+                                                       GTK_TEXT_BUFFER (buffer),
+                                                       location,
+                                                       text,
+                                                       len);
+               gtk_source_view_snippets_unblock (snippets);
+       }
+}
+
+static void
+buffer_insert_text_after_cb (GtkTextBuffer         *buffer,
+                             GtkTextIter           *location,
+                             const gchar           *text,
+                             gint                   len,
+                             GtkSourceViewSnippets *snippets)
+{
+       GtkSourceSnippet *snippet;
+
+       g_assert (GTK_IS_TEXT_BUFFER (buffer));
+       g_assert (location != NULL);
+       g_assert (text != NULL);
+       g_assert (snippets != NULL);
+
+       snippet = g_queue_peek_head (&snippets->queue);
+
+       if (snippet != NULL)
+       {
+               gtk_source_view_snippets_block (snippets);
+               _gtk_source_snippet_after_insert_text (snippet,
+                                                      GTK_TEXT_BUFFER (buffer),
+                                                      location,
+                                                      text,
+                                                      len);
+               gtk_source_view_snippets_unblock (snippets);
+
+               /* Copmlete our action from the before phase */
+               gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer));
+       }
+}
+
+static void
+buffer_delete_range_cb (GtkTextBuffer         *buffer,
+                        GtkTextIter           *begin,
+                        GtkTextIter           *end,
+                        GtkSourceViewSnippets *snippets)
+{
+       GtkSourceSnippet *snippet;
+
+       g_assert (GTK_IS_TEXT_BUFFER (buffer));
+       g_assert (begin != NULL);
+       g_assert (end != NULL);
+
+       snippet = g_queue_peek_head (&snippets->queue);
+
+       if (snippet != NULL)
+       {
+               /* We'll complete the user action in the after phase */
+               gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer));
+
+               gtk_source_view_snippets_block (snippets);
+               _gtk_source_snippet_before_delete_range (snippet,
+                                                        GTK_TEXT_BUFFER (buffer),
+                                                        begin,
+                                                        end);
+               gtk_source_view_snippets_unblock (snippets);
+       }
+}
+
+static void
+buffer_delete_range_after_cb (GtkTextBuffer         *buffer,
+                              GtkTextIter           *begin,
+                              GtkTextIter           *end,
+                              GtkSourceViewSnippets *snippets)
+{
+       GtkSourceSnippet *snippet;
+
+       g_assert (GTK_IS_TEXT_BUFFER (buffer));
+       g_assert (begin != NULL);
+       g_assert (end != NULL);
+
+       snippet = g_queue_peek_head (&snippets->queue);
+
+       if (snippet != NULL)
+       {
+               gtk_source_view_snippets_block (snippets);
+               _gtk_source_snippet_after_delete_range (snippet,
+                                                       GTK_TEXT_BUFFER (buffer),
+                                                       begin,
+                                                       end);
+               gtk_source_view_snippets_unblock (snippets);
+
+               /* Copmlete our action from the before phase */
+               gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer));
+       }
+}
+
+static void
+view_notify_buffer_cb (GtkSourceView         *view,
+                       GParamSpec            *pspec,
+                       GtkSourceViewSnippets *snippets)
+{
+       g_assert (view != NULL);
+       g_assert (snippets != NULL);
+       g_assert (snippets->view == view);
+
+       g_queue_clear_full (&snippets->queue, g_object_unref);
+
+       g_clear_signal_handler (&snippets->buffer_insert_text_handler,
+                               snippets->buffer);
+       g_clear_signal_handler (&snippets->buffer_insert_text_after_handler,
+                               snippets->buffer);
+       g_clear_signal_handler (&snippets->buffer_delete_range_handler,
+                               snippets->buffer);
+       g_clear_signal_handler (&snippets->buffer_delete_range_after_handler,
+                               snippets->buffer);
+
+       snippets->buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+
+       if (snippets->buffer != NULL)
+       {
+               snippets->buffer_insert_text_handler =
+                       g_signal_connect (snippets->buffer,
+                                         "insert-text",
+                                         G_CALLBACK (buffer_insert_text_cb),
+                                         snippets);
+               snippets->buffer_insert_text_after_handler =
+                       g_signal_connect_after (snippets->buffer,
+                                               "insert-text",
+                                               G_CALLBACK (buffer_insert_text_after_cb),
+                                               snippets);
+               snippets->buffer_delete_range_handler =
+                       g_signal_connect (snippets->buffer,
+                                         "delete-range",
+                                         G_CALLBACK (buffer_delete_range_cb),
+                                         snippets);
+               snippets->buffer_delete_range_after_handler =
+                       g_signal_connect_after (snippets->buffer,
+                                               "delete-range",
+                                               G_CALLBACK (buffer_delete_range_after_cb),
+                                               snippets);
+       }
+}
+
+void
+_gtk_source_view_snippets_init (GtkSourceViewSnippets *snippets,
+                                GtkSourceView         *view)
+{
+       g_return_if_fail (snippets != NULL);
+       g_return_if_fail (GTK_SOURCE_IS_VIEW (view));
+
+       memset (snippets, 0, sizeof *snippets);
+
+       snippets->view = view;
+       snippets->view_notify_buffer_handler =
+               g_signal_connect (view,
+                                 "notify::buffer",
+                                 G_CALLBACK (view_notify_buffer_cb),
+                                 snippets);
+
+       view_notify_buffer_cb (view, NULL, snippets);
+}
+
+void
+_gtk_source_view_snippets_shutdown (GtkSourceViewSnippets *snippets)
+{
+       g_return_if_fail (snippets != NULL);
+
+       g_queue_clear_full (&snippets->queue, g_object_unref);
+
+       g_clear_signal_handler (&snippets->view_notify_buffer_handler,
+                               snippets->view);
+       g_clear_signal_handler (&snippets->buffer_insert_text_handler,
+                               snippets->buffer);
+       g_clear_signal_handler (&snippets->buffer_insert_text_after_handler,
+                               snippets->buffer);
+       g_clear_signal_handler (&snippets->buffer_delete_range_handler,
+                               snippets->buffer);
+       g_clear_signal_handler (&snippets->buffer_delete_range_after_handler,
+                               snippets->buffer);
+
+       snippets->buffer = NULL;
+       snippets->view = NULL;
+}
+
+static GtkSourceSnippet *
+lookup_snippet_by_trigger (GtkSourceViewSnippets *snippets,
+                           const gchar           *word)
+{
+       GtkSourceSnippetChunk *chunk;
+       GtkSourceSnippet *snippet;
+
+       g_assert (snippets != NULL);
+       g_assert (word != NULL);
+
+       snippet = gtk_source_snippet_new (word, NULL);
+
+       for (guint i = 0; i < 3; i++)
+       {
+               chunk = gtk_source_snippet_chunk_new ();
+               if (i == 0)
+                       gtk_source_snippet_chunk_set_spec (chunk, word);
+               else
+                       gtk_source_snippet_chunk_set_spec (chunk, "$1");
+               gtk_source_snippet_chunk_set_focus_position (chunk, i+1);
+               gtk_source_snippet_add_chunk (snippet, chunk);
+               g_object_unref (chunk);
+       }
+
+       chunk = gtk_source_snippet_chunk_new ();
+       gtk_source_snippet_chunk_set_focus_position (chunk, 0);
+       gtk_source_snippet_add_chunk (snippet, chunk);
+       g_object_unref (chunk);
+
+       return snippet;
+}
+
+static gboolean
+gtk_source_view_snippets_try_expand (GtkSourceViewSnippets *snippets,
+                                     GtkTextIter           *iter)
+{
+       GtkSourceSnippet *snippet;
+       GtkTextIter begin;
+       gchar *word;
+
+       g_assert (snippets != NULL);
+       g_assert (iter != NULL);
+
+       if (gtk_text_iter_starts_line (iter) ||
+           !_gtk_source_iter_ends_full_word (iter))
+       {
+               return FALSE;
+       }
+
+       begin = *iter;
+
+       _gtk_source_iter_backward_full_word_start (&begin);
+
+       if (gtk_text_iter_compare (&begin, iter) >= 0)
+       {
+               return FALSE;
+       }
+
+       word = gtk_text_iter_get_slice (&begin, iter);
+
+       if (word == NULL || *word == 0)
+       {
+               return FALSE;
+       }
+
+       snippet = lookup_snippet_by_trigger (snippets, word);
+
+       g_free (word);
+
+       if (snippet != NULL)
+       {
+               _gtk_source_view_snippets_push (snippets, snippet, iter);
+               g_object_unref (snippet);
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+gboolean
+_gtk_source_view_snippets_key_pressed (GtkSourceViewSnippets *snippets,
+                                       guint                  key,
+                                       guint                  keycode,
+                                       GdkModifierType        state)
+{
+       GdkModifierType modifiers;
+       gboolean editable;
+
+       g_return_val_if_fail (snippets != NULL, FALSE);
+       g_return_val_if_fail (snippets->view != NULL, FALSE);
+
+       /* Be careful when testing for modifier state equality:
+        * caps lock, num lock,etc need to be taken into account */
+       modifiers = gtk_accelerator_get_default_mod_mask ();
+       editable = gtk_text_view_get_editable (GTK_TEXT_VIEW (snippets->view));
+
+       if ((key == GDK_KEY_Tab || key == GDK_KEY_KP_Tab || key == GDK_KEY_ISO_Left_Tab) &&
+           ((state & modifiers) == 0 ||
+            (state & modifiers) == GDK_SHIFT_MASK) &&
+           editable &&
+           gtk_text_view_get_accepts_tab (GTK_TEXT_VIEW (snippets->view)))
+       {
+               GtkSourceSnippet *snippet = g_queue_peek_head (&snippets->queue);
+               GtkTextIter begin, end;
+               gboolean has_selection;
+
+               /* If we already have a snippet expanded, then we might need
+                * to move forward or backward between snippet positions.
+                */
+               if (snippet != NULL)
+               {
+                       if ((state & modifiers) == 0)
+                       {
+                               if (!_gtk_source_snippet_move_next (snippet))
+                               {
+                                       _gtk_source_view_snippets_pop (snippets);
+                               }
+
+                               return GDK_EVENT_STOP;
+                       }
+                       else if (state & GDK_SHIFT_MASK)
+                       {
+                               if (!_gtk_source_snippet_move_previous (snippet))
+                               {
+                                       _gtk_source_view_snippets_pop (snippets);
+                               }
+
+                               return GDK_EVENT_STOP;
+                       }
+               }
+
+               has_selection = gtk_text_buffer_get_selection_bounds (snippets->buffer,
+                                                                     &begin, &end);
+
+               /* tab: if there is no selection and the current word is a
+                * snippet trigger, then we should expand that snippet.
+                */
+               if ((state & modifiers) == 0 &&
+                   !has_selection &&
+                   gtk_source_view_snippets_try_expand (snippets, &end))
+               {
+                       return GDK_EVENT_STOP;
+               }
+       }
+
+       return GDK_EVENT_PROPAGATE;
+}
+
+void
+_gtk_source_view_snippets_push (GtkSourceViewSnippets *snippets,
+                                GtkSourceSnippet      *snippet,
+                                GtkTextIter           *iter)
+{
+       GtkSourceSnippet *previous_snippet;
+       gboolean more_to_focus;
+
+       g_assert (snippets != NULL);
+       g_assert (GTK_SOURCE_IS_SNIPPET (snippet));
+       g_assert (iter != NULL);
+
+       previous_snippet = g_queue_peek_head (&snippets->queue);
+
+       if (previous_snippet != NULL)
+       {
+               _gtk_source_snippet_pause (previous_snippet);
+       }
+
+       g_queue_push_head (&snippets->queue, g_object_ref (snippet));
+
+       gtk_text_buffer_begin_user_action (snippets->buffer);
+       gtk_source_view_snippets_block (snippets);
+       more_to_focus = _gtk_source_snippet_begin (snippet, snippets->buffer, iter);
+       gtk_source_view_snippets_unblock (snippets);
+       gtk_text_buffer_end_user_action (snippets->buffer);
+
+       gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW (snippets->view),
+                                           gtk_text_buffer_get_insert (snippets->buffer));
+
+       if (!more_to_focus)
+       {
+               _gtk_source_view_snippets_pop (snippets);
+       }
+}
+
+void
+_gtk_source_view_snippets_pop (GtkSourceViewSnippets *snippets)
+{
+       GtkSourceSnippet *next_snippet;
+       GtkSourceSnippet *snippet;
+
+       g_assert (snippets != NULL);
+
+       snippet = g_queue_pop_head (&snippets->queue);
+
+       if (snippet != NULL)
+       {
+               next_snippet = g_queue_peek_head (&snippets->queue);
+
+               if (next_snippet != NULL)
+               {
+                       gchar *new_text;
+
+                       new_text = _gtk_source_snippet_get_edited_text (snippet);
+                       _gtk_source_snippet_replace_current_chunk_text (next_snippet, new_text);
+                       _gtk_source_snippet_unpause (next_snippet);
+                       _gtk_source_snippet_move_next (next_snippet);
+
+                       g_free (new_text);
+               }
+
+               g_object_unref (snippet);
+       }
+}
diff --git a/gtksourceview/gtksourceview.c b/gtksourceview/gtksourceview.c
index fb0018ab..2f5bd29f 100644
--- a/gtksourceview/gtksourceview.c
+++ b/gtksourceview/gtksourceview.c
@@ -22,8 +22,6 @@
 
 #include "config.h"
 
-#include "gtksourceview.h"
-
 #include <string.h>
 #include <fribidi.h>
 #include <gtk/gtk.h>
@@ -54,6 +52,7 @@
 #include "gtksourcespacedrawer-private.h"
 #include "gtksourcespacedrawer.h"
 #include "gtksourcestylescheme-private.h"
+#include "gtksourceview-private.h"
 
 /**
  * SECTION:view
@@ -161,7 +160,6 @@ enum
        MOVE_TO_MATCHING_BRACKET,
        MOVE_WORDS,
        PUSH_SNIPPET,
-       POP_SNIPPET,
        SHOW_COMPLETION,
        SMART_HOME_END,
        N_SIGNALS
@@ -209,7 +207,7 @@ typedef struct
 
        GtkSourceCompletion *completion;
 
-       GQueue snippets;
+       GtkSourceViewSnippets snippets;
 
        guint right_margin_pos;
        gint cached_right_margin_pos;
@@ -322,7 +320,6 @@ static void           gtk_source_view_populate_extra_menu  (GtkSourceView
 static void           gtk_source_view_real_push_snippet    (GtkSourceView           *view,
                                                             GtkSourceSnippet        *snippet,
                                                             GtkTextIter             *location);
-static void           gtk_source_view_pop_snippet          (GtkSourceView           *view);
 
 static void
 gtk_source_view_constructed (GObject *object)
@@ -861,27 +858,6 @@ gtk_source_view_class_init (GtkSourceViewClass *klass)
                                    G_TYPE_FROM_CLASS (klass),
                                    _gtk_source_marshal_VOID__OBJECT_BOXEDv);
 
-       /**
-        * GtkSourceView::pop-snippet:
-        * @view: a #GtkSourceView
-        * @snippet: a #GtkSourceSnippet
-        *
-        * The ::pop-snippet signal is emitted when a snippet's focus positions
-        * have been exhausted.
-        *
-        * Since: 5.0
-        */
-       signals[POP_SNIPPET] =
-               g_signal_new ("pop-snippet",
-                             G_TYPE_FROM_CLASS (klass),
-                             G_SIGNAL_RUN_LAST,
-                             G_STRUCT_OFFSET (GtkSourceViewClass, pop_snippet),
-                             NULL, NULL,
-                             NULL,
-                             G_TYPE_NONE,
-                             1,
-                             GTK_SOURCE_TYPE_SNIPPET);
-
        /**
         * GtkSourceView::smart-home-end:
         * @view: the #GtkSourceView
@@ -1404,6 +1380,8 @@ gtk_source_view_init (GtkSourceView *view)
        gtk_style_context_add_class (context, "sourceview");
 
        gtk_source_view_populate_extra_menu (view);
+
+       _gtk_source_view_snippets_init (&priv->snippets, view);
 }
 
 static void
@@ -1418,6 +1396,9 @@ gtk_source_view_dispose (GObject *object)
 
        remove_source_buffer (view);
 
+       /* Release our snippet state. This is safe to call multiple times. */
+       _gtk_source_view_snippets_shutdown (&priv->snippets);
+
        /* Disconnect notify buffer because the destroy of the textview will set
         * the buffer to NULL, and we call get_buffer in the notify which would
         * reinstate a buffer which we don't want.
@@ -1611,116 +1592,6 @@ implicit_trailing_newline_changed_cb (GtkSourceBuffer *buffer,
        gtk_source_view_queue_draw (view);
 }
 
-static void
-buffer_insert_text_cb (GtkSourceBuffer *buffer,
-                       GtkTextIter     *location,
-                       gchar           *text,
-                       gint             len,
-                       GtkSourceView   *view)
-{
-       GtkSourceViewPrivate *priv = gtk_source_view_get_instance_private (view);
-       GtkSourceSnippet *snippet;
-
-       g_assert (GTK_SOURCE_IS_BUFFER (buffer));
-       g_assert (location != NULL);
-       g_assert (text != NULL);
-       g_assert (GTK_SOURCE_IS_VIEW (view));
-
-       snippet = g_queue_peek_head (&priv->snippets);
-
-       if (snippet != NULL)
-       {
-               gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer));
-               _gtk_source_snippet_before_insert_text (snippet,
-                                                       GTK_TEXT_BUFFER (buffer),
-                                                       location,
-                                                       text,
-                                                       len);
-       }
-}
-
-static void
-buffer_after_insert_text_cb (GtkSourceBuffer *buffer,
-                             GtkTextIter     *location,
-                             gchar           *text,
-                             gint             len,
-                             GtkSourceView   *view)
-{
-       GtkSourceViewPrivate *priv = gtk_source_view_get_instance_private (view);
-       GtkSourceSnippet *snippet;
-
-       g_assert (GTK_SOURCE_IS_BUFFER (buffer));
-       g_assert (location != NULL);
-       g_assert (text != NULL);
-       g_assert (GTK_SOURCE_IS_VIEW (view));
-
-       snippet = g_queue_peek_head (&priv->snippets);
-
-       if (snippet != NULL)
-       {
-               _gtk_source_snippet_after_insert_text (snippet,
-                                                      GTK_TEXT_BUFFER (buffer),
-                                                      location,
-                                                      text,
-                                                      len);
-               gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer));
-       }
-}
-
-static void
-buffer_delete_range_cb (GtkSourceBuffer *buffer,
-                        GtkTextIter     *begin,
-                        GtkTextIter     *end,
-                        GtkSourceView   *view)
-{
-       GtkSourceViewPrivate *priv = gtk_source_view_get_instance_private (view);
-       GtkSourceSnippet *snippet;
-
-       g_assert (GTK_SOURCE_IS_BUFFER (buffer));
-       g_assert (begin != NULL);
-       g_assert (end != NULL);
-       g_assert (GTK_SOURCE_IS_VIEW (view));
-
-       snippet = g_queue_peek_head (&priv->snippets);
-
-       if (snippet != NULL)
-       {
-               //ide_source_view_block_handlers (self);
-               _gtk_source_snippet_before_delete_range (snippet,
-                                                        GTK_TEXT_BUFFER (buffer),
-                                                        begin,
-                                                        end);
-               //ide_source_view_unblock_handlers (self);
-       }
-}
-
-static void
-buffer_delete_range_after_cb (GtkSourceBuffer *buffer,
-                              GtkTextIter     *begin,
-                              GtkTextIter     *end,
-                              GtkSourceView   *view)
-{
-       GtkSourceViewPrivate *priv = gtk_source_view_get_instance_private (view);
-       GtkSourceSnippet *snippet;
-
-       g_assert (GTK_SOURCE_IS_BUFFER (buffer));
-       g_assert (begin != NULL);
-       g_assert (end != NULL);
-       g_assert (GTK_SOURCE_IS_VIEW (view));
-
-       snippet = g_queue_peek_head (&priv->snippets);
-
-       if (snippet != NULL)
-       {
-               //ide_source_view_block_handlers (self);
-               _gtk_source_snippet_after_delete_range (snippet,
-                                                       GTK_TEXT_BUFFER (buffer),
-                                                       begin,
-                                                       end);
-               //ide_source_view_unblock_handlers (self);
-       }
-}
-
 static void
 remove_source_buffer (GtkSourceView *view)
 {
@@ -1750,14 +1621,6 @@ remove_source_buffer (GtkSourceView *view)
                                                      implicit_trailing_newline_changed_cb,
                                                      view);
 
-               g_signal_handlers_disconnect_by_func (priv->source_buffer,
-                                                     buffer_insert_text_cb,
-                                                     view);
-
-               g_signal_handlers_disconnect_by_func (priv->source_buffer,
-                                                     buffer_after_insert_text_cb,
-                                                     view);
-
                buffer_internal = _gtk_source_buffer_internal_get_from_buffer (priv->source_buffer);
 
                g_signal_handlers_disconnect_by_func (buffer_internal,
@@ -1813,16 +1676,6 @@ set_source_buffer (GtkSourceView *view,
                                  G_CALLBACK (buffer_has_selection_changed_cb),
                                  view);
 
-               g_signal_connect (buffer,
-                                 "insert-text",
-                                 G_CALLBACK (buffer_insert_text_cb),
-                                 view);
-
-               g_signal_connect_after (buffer,
-                                       "insert-text",
-                                       G_CALLBACK (buffer_after_insert_text_cb),
-                                       view);
-
                buffer_internal = _gtk_source_buffer_internal_get_from_buffer (priv->source_buffer);
 
                g_signal_connect (buffer_internal,
@@ -4028,82 +3881,6 @@ do_ctrl_backspace (GtkSourceView *view)
        return FALSE;
 }
 
-static GtkSourceSnippet *
-lookup_snippet_by_trigger (GtkSourceView *view,
-                           const gchar   *word)
-{
-       GtkSourceSnippet *snippet;
-       GtkSourceSnippetChunk *chunk;
-
-       g_assert (GTK_SOURCE_IS_VIEW (view));
-       g_assert (word != NULL);
-
-       snippet = gtk_source_snippet_new (word, NULL);
-
-       for (guint i = 0; i < 3; i++)
-       {
-               chunk = gtk_source_snippet_chunk_new ();
-               gtk_source_snippet_chunk_set_spec (chunk, word);
-               gtk_source_snippet_chunk_set_focus_position (chunk, i+1);
-               gtk_source_snippet_add_chunk (snippet, chunk);
-               g_object_unref (chunk);
-       }
-
-       chunk = gtk_source_snippet_chunk_new ();
-       gtk_source_snippet_chunk_set_focus_position (chunk, 0);
-       gtk_source_snippet_add_chunk (snippet, chunk);
-       g_object_unref (chunk);
-
-       return snippet;
-}
-
-static gboolean
-gtk_source_view_try_expand_snippet (GtkSourceView *view,
-                                    GtkTextIter   *iter)
-{
-       GtkSourceSnippet *snippet;
-       GtkTextIter begin;
-       gchar *word;
-
-       g_assert (GTK_SOURCE_IS_VIEW (view));
-       g_assert (iter != NULL);
-
-       if (gtk_text_iter_starts_line (iter) ||
-           !_gtk_source_iter_ends_full_word (iter))
-       {
-               return FALSE;
-       }
-
-       begin = *iter;
-
-       _gtk_source_iter_backward_full_word_start (&begin);
-
-       if (gtk_text_iter_compare (&begin, iter) >= 0)
-       {
-               return FALSE;
-       }
-
-       word = gtk_text_iter_get_slice (&begin, iter);
-
-       if (word == NULL || *word == 0)
-       {
-               return FALSE;
-       }
-
-       snippet = lookup_snippet_by_trigger (view, word);
-
-       g_free (word);
-
-       if (snippet != NULL)
-       {
-               gtk_source_view_push_snippet (view, snippet, iter);
-               g_object_unref (snippet);
-               return TRUE;
-       }
-
-       return FALSE;
-}
-
 static gboolean
 gtk_source_view_key_pressed (GtkSourceView         *view,
                              guint                  key,
@@ -4166,6 +3943,11 @@ gtk_source_view_key_pressed (GtkSourceView         *view,
                }
        }
 
+       if (_gtk_source_view_snippets_key_pressed (&priv->snippets, key, keycode, state))
+       {
+               return GDK_EVENT_STOP;
+       }
+
        /* if tab or shift+tab:
         * with shift+tab key is GDK_ISO_Left_Tab (yay! on win32 and mac too!)
         */
@@ -4175,47 +3957,11 @@ gtk_source_view_key_pressed (GtkSourceView         *view,
            editable &&
            gtk_text_view_get_accepts_tab (GTK_TEXT_VIEW (view)))
        {
-               GtkSourceSnippet *snippet = g_queue_peek_head (&priv->snippets);
                GtkTextIter s, e;
                gboolean has_selection;
 
                has_selection = gtk_text_buffer_get_selection_bounds (buf, &s, &e);
 
-               /* If we already have a snippet expanded, then we might need
-                * to move forward or backward between snippet positions.
-                */
-               if (snippet != NULL)
-               {
-                       if (state == 0)
-                       {
-                               if (!_gtk_source_snippet_move_next (snippet))
-                               {
-                                       gtk_source_view_pop_snippet (view);
-                               }
-
-                               return GDK_EVENT_STOP;
-                       }
-                       else if (state & GDK_SHIFT_MASK)
-                       {
-                               if (!_gtk_source_snippet_move_previous (snippet))
-                               {
-                                       gtk_source_view_pop_snippet (view);
-                               }
-
-                               return GDK_EVENT_STOP;
-                       }
-               }
-
-               /* tab: if there is no selection and the current word is a
-                * snippet trigger, then we should expand that snippet.
-                */
-               if (state == 0 &&
-                   !has_selection &&
-                   gtk_source_view_try_expand_snippet (view, &e))
-               {
-                       return GDK_EVENT_STOP;
-               }
-
                if (priv->indent_on_tab)
                {
                        /* shift+tab: always unindent */
@@ -5281,28 +5027,12 @@ gtk_source_view_real_push_snippet (GtkSourceView    *view,
                                    GtkTextIter      *location)
 {
        GtkSourceViewPrivate *priv = gtk_source_view_get_instance_private (view);
-       GtkTextBuffer *buffer;
-       gboolean more_to_focus;
 
        g_assert (GTK_SOURCE_IS_VIEW (view));
        g_assert (GTK_SOURCE_IS_SNIPPET (snippet));
        g_assert (location != NULL);
 
-       buffer = gtk_text_iter_get_buffer (location);
-
-       g_queue_push_head (&priv->snippets, g_object_ref (snippet));
-
-       gtk_text_buffer_begin_user_action (buffer);
-       more_to_focus = _gtk_source_snippet_begin (snippet, buffer, location);
-       gtk_text_buffer_end_user_action (buffer);
-
-       gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW (view),
-                                           gtk_text_buffer_get_insert (buffer));
-
-       if (!more_to_focus)
-       {
-               gtk_source_view_pop_snippet (view);
-       }
+       _gtk_source_view_snippets_push (&priv->snippets, snippet, location);
 }
 
 /**
@@ -5324,9 +5054,7 @@ gtk_source_view_push_snippet (GtkSourceView    *view,
                               GtkSourceSnippet *snippet,
                               GtkTextIter      *location)
 {
-       GtkSourceViewPrivate *priv = gtk_source_view_get_instance_private (view);
        GtkSourceSnippetContext *context;
-       GtkSourceSnippet *previous_snippet;
        GtkTextBuffer *buffer;
        GtkTextIter iter;
        gboolean use_spaces;
@@ -5336,13 +5064,6 @@ gtk_source_view_push_snippet (GtkSourceView    *view,
        g_return_if_fail (GTK_SOURCE_IS_VIEW (view));
        g_return_if_fail (GTK_SOURCE_IS_SNIPPET (snippet));
 
-       previous_snippet = g_queue_peek_head (&priv->snippets);
-
-       if (previous_snippet != NULL)
-       {
-               _gtk_source_snippet_pause (previous_snippet);
-       }
-
        buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
 
        if (location == NULL)
@@ -5369,38 +5090,3 @@ gtk_source_view_push_snippet (GtkSourceView    *view,
 
        g_signal_emit (view, signals [PUSH_SNIPPET], 0, snippet, location);
 }
-
-static void
-gtk_source_view_pop_snippet (GtkSourceView *view)
-{
-       GtkSourceViewPrivate *priv = gtk_source_view_get_instance_private (view);
-       GtkSourceSnippet *next_snippet;
-       GtkSourceSnippet *snippet;
-
-       g_return_if_fail (GTK_SOURCE_IS_VIEW (view));
-
-       snippet = g_queue_pop_head (&priv->snippets);
-
-       if (snippet == NULL)
-       {
-               return;
-       }
-
-       next_snippet = g_queue_peek_head (&priv->snippets);
-
-       if (next_snippet != NULL)
-       {
-               gchar *new_text;
-
-               new_text = _gtk_source_snippet_get_edited_text (snippet);
-               _gtk_source_snippet_replace_current_chunk_text (next_snippet, new_text);
-               _gtk_source_snippet_unpause (next_snippet);
-               _gtk_source_snippet_move_next (next_snippet);
-
-               g_free (new_text);
-       }
-
-       g_signal_emit (view, signals[POP_SNIPPET], 0, snippet);
-
-       g_object_unref (snippet);
-}
diff --git a/gtksourceview/gtksourceview.h b/gtksourceview/gtksourceview.h
index a7a719c2..367e8b2c 100644
--- a/gtksourceview/gtksourceview.h
+++ b/gtksourceview/gtksourceview.h
@@ -99,8 +99,6 @@ struct _GtkSourceViewClass
        void (*push_snippet)        (GtkSourceView     *view,
                                     GtkSourceSnippet  *snippet,
                                     GtkTextIter       *location);
-       void (*pop_snippet)         (GtkSourceView     *view,
-                                    GtkSourceSnippet  *snippet);
 
        /*< private >*/
        gpointer _reserved[20];
diff --git a/gtksourceview/meson.build b/gtksourceview/meson.build
index 7aca6f1b..5073577a 100644
--- a/gtksourceview/meson.build
+++ b/gtksourceview/meson.build
@@ -106,6 +106,7 @@ core_private_c = files([
   'gtksourcemarkssequence.c',
   'gtksourcepixbufhelper.c',
   'gtksourceregex.c',
+  'gtksourceview-snippets.c',
 ])
 
 core_c_args = [


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