[gtksourceview/wip/chergert/snippets] more work on switching to marks



commit cb9c4d6e4b3927196bbca8b37152c2c453d382c8
Author: Christian Hergert <chergert redhat com>
Date:   Thu Jan 23 12:09:48 2020 -0800

    more work on switching to marks

 gtksourceview/gtksourcesnippet.c              | 378 ++++++++++++++++++--------
 gtksourceview/gtksourcesnippetchunk-private.h |   2 +-
 gtksourceview/gtksourcesnippetchunk.c         |  33 ++-
 3 files changed, 288 insertions(+), 125 deletions(-)
---
diff --git a/gtksourceview/gtksourcesnippet.c b/gtksourceview/gtksourcesnippet.c
index ae190faa..c8692791 100644
--- a/gtksourceview/gtksourcesnippet.c
+++ b/gtksourceview/gtksourcesnippet.c
@@ -41,6 +41,14 @@ struct _GtkSourceSnippet
        const gchar             *language_id;
        gchar                   *description;
 
+       /* This is used to track the insert position within a snippet
+        * while we make transforms. We don't use marks here because
+        * the gravity of the mark is not enought o assure we end up
+        * at the correct position. So instead we are relative to the
+        * beginning of the snippet.
+        */
+       gint                     saved_insert_pos;
+
        gint                     focus_position;
        gint                     max_focus_position;
 
@@ -63,6 +71,29 @@ enum {
 
 static GParamSpec *properties [N_PROPS];
 
+static void gtk_source_snippet_update_marks (GtkSourceSnippet *snippet);
+static void gtk_source_snippet_update_tags  (GtkSourceSnippet *self);
+
+static inline void
+print_chunk_positions (GtkSourceSnippet *snippet)
+{
+       for (guint i = 0; i < snippet->chunks->len; i++)
+       {
+               GtkSourceSnippetChunk *chunk = g_ptr_array_index (snippet->chunks, i);
+               GtkTextIter begin, end;
+
+               if (_gtk_source_snippet_chunk_get_bounds (chunk, &begin, &end))
+               {
+                       g_printerr ("Chunk %u: %u:%u to %u:%u\n",
+                                   i,
+                                   gtk_text_iter_get_line (&begin),
+                                   gtk_text_iter_get_line_offset (&begin),
+                                   gtk_text_iter_get_line (&end),
+                                   gtk_text_iter_get_line_offset (&end));
+               }
+       }
+}
+
 static GtkSourceSnippetChunk *
 get_chunk_at_iter (GtkSourceSnippet *self,
                    GtkTextIter      *iter)
@@ -96,6 +127,54 @@ get_chunk_at_iter (GtkSourceSnippet *self,
        g_return_val_if_reached (NULL);
 }
 
+static void
+gtk_source_snippet_save_insert (GtkSourceSnippet *snippet)
+{
+       GtkTextMark *insert;
+       GtkTextIter iter;
+       GtkTextIter begin;
+       GtkTextIter end;
+
+       g_assert (GTK_SOURCE_IS_SNIPPET (snippet));
+
+       if (snippet->current_chunk == NULL ||
+           !_gtk_source_snippet_chunk_get_bounds (snippet->current_chunk, &begin, &end))
+       {
+               snippet->saved_insert_pos = 0;
+               return;
+       }
+
+       insert = gtk_text_buffer_get_insert (snippet->buffer);
+       gtk_text_buffer_get_iter_at_mark (snippet->buffer, &iter, insert);
+
+       if (_gtk_source_snippet_chunk_contains (snippet->current_chunk, &iter))
+       {
+               snippet->saved_insert_pos =
+                       gtk_text_iter_get_offset (&iter) -
+                       gtk_text_iter_get_offset (&begin);
+       }
+}
+
+static void
+gtk_source_snippet_restore_insert (GtkSourceSnippet *snippet)
+{
+       GtkTextIter begin;
+       GtkTextIter end;
+
+       g_assert (GTK_SOURCE_IS_SNIPPET (snippet));
+
+       if (snippet->current_chunk == NULL ||
+           !_gtk_source_snippet_chunk_get_bounds (snippet->current_chunk, &begin, &end))
+       {
+               snippet->saved_insert_pos = 0;
+               return;
+       }
+
+       gtk_text_iter_forward_chars (&begin, snippet->saved_insert_pos);
+       gtk_text_buffer_select_range (snippet->buffer, &begin, &begin);
+       snippet->saved_insert_pos = 0;
+}
+
 /**
  * gtk_source_snippet_new:
  * @trigger: (nullable): the trigger word
@@ -639,6 +718,9 @@ _gtk_source_snippet_begin (GtkSourceSnippet *self,
        mark = gtk_text_buffer_create_mark (buffer, NULL, iter, TRUE);
        self->mark_begin = g_object_ref (mark);
 
+       mark = gtk_text_buffer_create_mark (buffer, NULL, iter, FALSE);
+       self->mark_end = g_object_ref (mark);
+
        gtk_text_buffer_begin_user_action (buffer);
 
        for (guint i = 0; i < self->chunks->len; i++)
@@ -652,19 +734,20 @@ _gtk_source_snippet_begin (GtkSourceSnippet *self,
                text = gtk_source_snippet_chunk_get_text (chunk);
 
                begin = gtk_text_buffer_create_mark (buffer, NULL, iter, TRUE);
+               end = gtk_text_buffer_create_mark (buffer, NULL, iter, FALSE);
+
                _gtk_source_snippet_chunk_set_begin_mark (chunk, begin);
+               _gtk_source_snippet_chunk_set_end_mark (chunk, end);
 
-               if (text != NULL)
+               if (text != NULL && text[0] != 0)
                {
+                       self->current_chunk = chunk;
                        gtk_text_buffer_insert (buffer, iter, text, -1);
+                       gtk_source_snippet_update_marks (self);
                }
-
-               end = gtk_text_buffer_create_mark (buffer, NULL, iter, FALSE);
-               _gtk_source_snippet_chunk_set_end_mark (chunk, end);
        }
 
-       mark = gtk_text_buffer_create_mark (buffer, NULL, iter, FALSE);
-       self->mark_end = g_object_ref (mark);
+       self->current_chunk = NULL;
 
        gtk_text_buffer_end_user_action (buffer);
 
@@ -704,48 +787,135 @@ gtk_source_snippet_add_chunk (GtkSourceSnippet      *self,
 }
 
 static void
-gtk_source_snippet_replace_chunk_text (GtkSourceSnippet      *self,
-                                       GtkSourceSnippetChunk *chunk,
-                                       const gchar           *text)
+gtk_source_snippet_update_marks (GtkSourceSnippet *snippet)
 {
-       GtkTextIter begin;
-       GtkTextIter end;
-       gint diff;
+       GtkTextBuffer *buffer;
 
-       g_assert (GTK_SOURCE_IS_SNIPPET (self));
-       g_assert (GTK_SOURCE_IS_SNIPPET_CHUNK (chunk));
-
-       /*
-        * This replaces the text for the snippet. We insert new text before
-        * we delete the old text to ensure things are more stable as we
-        * manipulate the runs. Avoiding zero-length runs, even temporarily
-        * can be helpful to reduce chances for textmark gravity overlapping
-        * other marks.
+       g_assert (GTK_SOURCE_IS_SNIPPET (snippet));
+
+       /* If the begin of this chunk has come before the end
+        * of the last chunk, then that mights we are empty and
+        * the right gravity of the begin mark was greedily taken
+        * when inserting into a previous mark. This can happen
+        * when you (often intermittently) have empty chunks.
+        *
+        * For example, imagine 4 empty chunks:
+        *
+        *   [][][][]
+        *
+        * Except in reality to GtkTextBuffer, that's more like:
+        *
+        *   [[[[]]]]
+        *
+        * When the user types 't' into the first chunk we'll end up
+        * with something like this:
+        *
+        *   [[[[t]]]]
+        *
+        * and we need to modify things to look like this:
+        *
+        *   [t][[[]]]
+        *
+        * We also must worry about the situation where text
+        * is inserted into the second position like:
+        *
+        *   [t[t]][[]]
+        *
+        * and detect the situation to move the end mark for the
+        * first item backwards into:
+        *
+        *   [t][t][[]]
         */
 
-       _gtk_source_snippet_chunk_get_bounds (chunk, &begin, &end);
+#if 1
+       g_printerr ("Before:\n");
+       print_chunk_positions (snippet);
+#endif
 
-       gtk_text_iter_order (&begin, &end);
-       diff = gtk_text_iter_get_offset (&end) - gtk_text_iter_get_offset (&begin);
+       if (snippet->chunks->len == 0)
+       {
+               return;
+       }
 
-       if (text != NULL)
+       buffer = GTK_TEXT_BUFFER (snippet->buffer);
+
+       /* For all marks before the current_chunk we want to ensure that their
+        * end_mark has not moved past the begin_mark for the chunk after it.
+        * That can happen when text is inserted at the beginning of a chunk as
+        * the right gravity mark from the adjacent chunk will be moved.
+        */
+       for (guint i = 0; i < snippet->chunks->len - 1; i++)
        {
-               gtk_text_buffer_insert (self->buffer, &begin, text, -1);
+               GtkSourceSnippetChunk *chunk = g_ptr_array_index (snippet->chunks, i);
+               GtkSourceSnippetChunk *next = g_ptr_array_index (snippet->chunks, i + 1);
+               GtkTextIter begin, end;
+               GtkTextIter next_begin, next_end;
+
+               if (!_gtk_source_snippet_chunk_get_bounds (chunk, &begin, &end) ||
+                   !_gtk_source_snippet_chunk_get_bounds (next, &next_begin, &next_end))
+               {
+                       break;
+               }
+
+               if (gtk_text_iter_compare (&end, &next_begin) > 0)
+               {
+                       gtk_text_buffer_move_mark (buffer,
+                                                  _gtk_source_snippet_chunk_get_end_mark (chunk),
+                                                  &next_begin);
+               }
+
+               if (chunk == snippet->current_chunk)
+               {
+                       break;
+               }
        }
 
-       if (diff > 0)
+#if 1
+       g_printerr ("After move up to current_chunk:\n");
+       print_chunk_positions (snippet);
+#endif
+
+       /* Now for all of the snippets, if their start position becomes before
+        * the previous end position, move it forward. This can happen when you
+        * have adjacent chunks and the gravity keeps the mark in the wrong
+        * position relative to other marks.
+        */
+       for (guint i = 1; i < snippet->chunks->len; i++)
        {
-               end = begin;
-               gtk_text_iter_forward_chars (&end, diff);
-               gtk_text_buffer_delete (self->buffer, &begin, &end);
+               GtkSourceSnippetChunk *chunk = g_ptr_array_index (snippet->chunks, i);
+               GtkSourceSnippetChunk *prev = g_ptr_array_index (snippet->chunks, i - 1);
+               GtkTextIter begin, end;
+               GtkTextIter prev_begin, prev_end;
+
+               if (!_gtk_source_snippet_chunk_get_bounds (chunk, &begin, &end) ||
+                   !_gtk_source_snippet_chunk_get_bounds (prev, &prev_begin, &prev_end))
+               {
+                       break;
+               }
+
+               if (gtk_text_iter_compare (&begin, &prev_end) < 0)
+               {
+                       gtk_text_buffer_move_mark (buffer,
+                                                  _gtk_source_snippet_chunk_get_begin_mark (chunk),
+                                                  &end);
+               }
        }
+
+#if 1
+       g_printerr ("After move of all:\n");
+       print_chunk_positions (snippet);
+#endif
 }
 
 static void
 gtk_source_snippet_rewrite_updated_chunks (GtkSourceSnippet *self)
 {
+       GtkSourceSnippetChunk *current;
+
        g_return_if_fail (GTK_SOURCE_IS_SNIPPET (self));
 
+       current = self->current_chunk;
+
        for (guint i = 0; i < self->chunks->len; i++)
        {
                GtkSourceSnippetChunk *chunk = g_ptr_array_index (self->chunks, i);
@@ -754,6 +924,11 @@ gtk_source_snippet_rewrite_updated_chunks (GtkSourceSnippet *self)
                const gchar *text;
                gchar *real_text;
 
+               /* Temporarily set current chunk to help other utilities
+                * to adjust marks appropriately.
+                */
+               self->current_chunk = chunk;
+
                _gtk_source_snippet_chunk_get_bounds (chunk, &begin, &end);
 
                text = gtk_source_snippet_chunk_get_text (chunk);
@@ -761,81 +936,21 @@ gtk_source_snippet_rewrite_updated_chunks (GtkSourceSnippet *self)
 
                if (g_strcmp0 (text, real_text) != 0)
                {
-                       gtk_source_snippet_replace_chunk_text (self, chunk, text);
+                       gtk_text_buffer_delete (self->buffer, &begin, &end);
+                       gtk_source_snippet_update_marks (self);
+
+                       gtk_text_buffer_insert (self->buffer, &begin, real_text, -1);
+                       gtk_source_snippet_update_marks (self);
                }
 
                g_free (real_text);
        }
-}
-
-static void
-gtk_source_snippet_update_marks (GtkSourceSnippet *snippet)
-{
-       GtkTextBuffer *buffer;
-       GtkTextMark *last_end = NULL;
-
-       g_assert (GTK_SOURCE_IS_SNIPPET (snippet));
-
-       buffer = GTK_TEXT_BUFFER (snippet->buffer);
-
-       for (guint i = 0; i < snippet->chunks->len; i++)
-       {
-               GtkSourceSnippetChunk *chunk;
-               GtkTextMark *begin_mark;
-               GtkTextMark *end_mark;
-               GtkTextIter begin;
-               GtkTextIter end;
-               GtkTextIter last;
 
-               chunk = g_ptr_array_index (snippet->chunks, i);
-               begin_mark = _gtk_source_snippet_chunk_get_begin_mark (chunk);
-               end_mark = _gtk_source_snippet_chunk_get_end_mark (chunk);
+       if (current)
+               g_print ("!!!! reseting current chunk to %d\n",
+                        gtk_source_snippet_chunk_get_focus_position (current));
 
-               if (last_end == NULL)
-               {
-                       last_end = end_mark;
-                       continue;
-               }
-
-               gtk_text_buffer_get_iter_at_mark (buffer, &begin, begin_mark);
-               gtk_text_buffer_get_iter_at_mark (buffer, &last, last_end);
-
-               /* If the begin of this chunk has come before the end
-                * of the last chunk, then that mights we are empty and
-                * the right gravity of the begin mark was greedily taken
-                * when inserting into a previous mark. This can happen
-                * when you (often intermittently) have empty chunks.
-                *
-                * For example, imagine 4 empty chunks:
-                *
-                *   [][][][]
-                *
-                * Except in reality to GtkTextBuffer, that's more like:
-                *
-                *   [[[[]]]]
-                *
-                * When the user types 't' into the first chunk we'll end up
-                * with something like this:
-                *
-                *   [[[[t]]]]
-                *
-                * and we need to modify things to look like this:
-                *
-                *   [t][[[]]]
-                */
-
-               if (gtk_text_iter_compare (&last, &begin) > 0)
-               {
-                       gtk_text_buffer_move_mark (buffer, begin_mark, &last);
-               }
-
-               if (gtk_text_iter_compare (&begin, &end) > 0)
-               {
-                       gtk_text_buffer_move_mark (buffer, end_mark, &begin);
-               }
-
-               last_end = end_mark;
-       }
+       self->current_chunk = current;
 }
 
 void
@@ -850,6 +965,10 @@ _gtk_source_snippet_before_insert_text (GtkSourceSnippet *self,
        g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
        g_return_if_fail (iter != NULL);
 
+       g_printerr ("Insert: %u:%u text=%s\n",
+                   gtk_text_iter_get_line (iter),
+                   gtk_text_iter_get_line_offset (iter),
+                   text);
 }
 
 void
@@ -860,31 +979,42 @@ _gtk_source_snippet_after_insert_text (GtkSourceSnippet *self,
                                        gint              len)
 {
        GtkSourceSnippetChunk *chunk;
-       GtkTextMark *end_mark;
-       GtkTextIter end;
 
        g_return_if_fail (GTK_SOURCE_IS_SNIPPET (self));
        g_return_if_fail (self->current_chunk != NULL);
        g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
        g_return_if_fail (iter != NULL);
 
-       chunk = get_chunk_at_iter (self, iter);
+       /* This function is guaranteed to only be called once for the
+        * actual insert by gtksourceview-snippets.c. That allows us
+        * to update marks, update the context for shared variables, and
+        * delete/insert text in linked chunks.
+        */
 
-       end_mark = _gtk_source_snippet_chunk_get_end_mark (chunk);
-       gtk_text_buffer_get_iter_at_mark (buffer, &end, end_mark);
+       gtk_source_snippet_save_insert (self);
 
-       if (gtk_text_iter_compare (iter, &end) > 0)
-       {
-               gtk_text_buffer_move_mark (buffer, end_mark, iter);
-       }
+       /* First we want to update marks from the inserted text */
+       gtk_source_snippet_update_marks (self);
 
+       /* Now save the modified text for the iter in question */
+       chunk = get_chunk_at_iter (self, iter);
        _gtk_source_snippet_chunk_save_text (chunk);
 
-       gtk_source_snippet_update_marks (self);
+       /* Update the context (two passes to ensure that we handle chunks
+        * referencing chunks which come after themselves in the array).
+        */
        gtk_source_snippet_update_context (self);
        gtk_source_snippet_update_context (self);
+
+       /* Now go and rewrite each chunk that has changed. This may also
+        * update marks after each pass so that text marks don't overlap.
+        */
        gtk_source_snippet_rewrite_updated_chunks (self);
+
+       /* Now we can apply tags for the given chunks */
        gtk_source_snippet_update_tags (self);
+
+       gtk_source_snippet_restore_insert (self);
 }
 
 void
@@ -893,12 +1023,21 @@ _gtk_source_snippet_before_delete_range (GtkSourceSnippet *self,
                                          GtkTextIter      *begin,
                                          GtkTextIter      *end)
 {
+       gchar *text;
 
        g_return_if_fail (GTK_SOURCE_IS_SNIPPET (self));
        g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
        g_return_if_fail (begin != NULL);
        g_return_if_fail (end != NULL);
 
+       text = gtk_text_iter_get_slice (begin, end);
+       g_printerr ("Deleging: %u:%u to %u:%u text=%s\n",
+                   gtk_text_iter_get_line (begin),
+                   gtk_text_iter_get_line_offset (begin),
+                   gtk_text_iter_get_line (end),
+                   gtk_text_iter_get_line_offset (end),
+                   text);
+       g_free (text);
 }
 
 void
@@ -907,15 +1046,36 @@ _gtk_source_snippet_after_delete_range (GtkSourceSnippet *self,
                                         GtkTextIter      *begin,
                                         GtkTextIter      *end)
 {
+       GtkSourceSnippetChunk *chunk;
+
        g_return_if_fail (GTK_SOURCE_IS_SNIPPET (self));
        g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
        g_return_if_fail (begin);
        g_return_if_fail (end);
 
+       gtk_source_snippet_save_insert (self);
+
+       /* First update mark positions based on the deletions */
+       gtk_source_snippet_update_marks (self);
+
+       /* Now save the modified text for the iter in question */
+       chunk = get_chunk_at_iter (self, begin);
+       _gtk_source_snippet_chunk_save_text (chunk);
+
+       /* Update the context (two passes to ensure that we handle chunks
+        * referencing chunks which come after themselves in the array).
+        */
        gtk_source_snippet_update_context (self);
        gtk_source_snippet_update_context (self);
+
+       /* Now go and rewrite each chunk that has changed. This may also
+        * update marks after each pass so that text marks don't overlap.
+        */
        gtk_source_snippet_rewrite_updated_chunks (self);
+
        gtk_source_snippet_update_tags (self);
+
+       gtk_source_snippet_restore_insert (self);
 }
 
 GtkTextMark *
diff --git a/gtksourceview/gtksourcesnippetchunk-private.h b/gtksourceview/gtksourcesnippetchunk-private.h
index 904302c0..071b7cbb 100644
--- a/gtksourceview/gtksourcesnippetchunk-private.h
+++ b/gtksourceview/gtksourcesnippetchunk-private.h
@@ -32,7 +32,7 @@ void         _gtk_source_snippet_chunk_set_end_mark   (GtkSourceSnippetChunk *ch
 void         _gtk_source_snippet_chunk_save_text      (GtkSourceSnippetChunk *chunk);
 gboolean     _gtk_source_snippet_chunk_contains       (GtkSourceSnippetChunk *chunk,
                                                        const GtkTextIter     *iter);
-void         _gtk_source_snippet_chunk_get_bounds     (GtkSourceSnippetChunk *chunk,
+gboolean     _gtk_source_snippet_chunk_get_bounds     (GtkSourceSnippetChunk *chunk,
                                                        GtkTextIter           *begin,
                                                        GtkTextIter           *end);
 
diff --git a/gtksourceview/gtksourcesnippetchunk.c b/gtksourceview/gtksourcesnippetchunk.c
index 79f45208..9606572b 100644
--- a/gtksourceview/gtksourcesnippetchunk.c
+++ b/gtksourceview/gtksourcesnippetchunk.c
@@ -432,7 +432,7 @@ _gtk_source_snippet_chunk_get_end_mark (GtkSourceSnippetChunk *chunk)
 {
        g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET_CHUNK (chunk), NULL);
 
-       return chunk->begin_mark;
+       return chunk->end_mark;
 }
 
 void
@@ -455,21 +455,28 @@ _gtk_source_snippet_chunk_set_end_mark (GtkSourceSnippetChunk *chunk,
        g_set_object (&chunk->end_mark, end_mark);
 }
 
-void
+gboolean
 _gtk_source_snippet_chunk_get_bounds (GtkSourceSnippetChunk *chunk,
                                       GtkTextIter           *begin,
                                       GtkTextIter           *end)
 {
        GtkTextBuffer *buffer;
 
-       g_return_if_fail (GTK_SOURCE_IS_SNIPPET_CHUNK (chunk));
-       g_return_if_fail (begin != NULL);
-       g_return_if_fail (end != NULL);
+       g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET_CHUNK (chunk), FALSE);
+       g_return_val_if_fail (begin != NULL, FALSE);
+       g_return_val_if_fail (end != NULL, FALSE);
+
+       if (chunk->begin_mark == NULL || chunk->end_mark == NULL)
+       {
+               return FALSE;
+       }
 
        buffer = gtk_text_mark_get_buffer (chunk->begin_mark);
 
        gtk_text_buffer_get_iter_at_mark (buffer, begin, chunk->begin_mark);
        gtk_text_buffer_get_iter_at_mark (buffer, end, chunk->end_mark);
+
+       return TRUE;
 }
 
 void
@@ -509,15 +516,11 @@ _gtk_source_snippet_chunk_contains (GtkSourceSnippetChunk *chunk,
        g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET_CHUNK (chunk), FALSE);
        g_return_val_if_fail (iter != NULL, FALSE);
 
-       _gtk_source_snippet_chunk_get_bounds (chunk, &begin, &end);
-
-#if 0
-       g_print ("Is %d between %d and %d\n",
-                gtk_text_iter_get_offset (iter),
-                gtk_text_iter_get_offset (&begin),
-                gtk_text_iter_get_offset (&end));
-#endif
+       if (_gtk_source_snippet_chunk_get_bounds (chunk, &begin, &end))
+       {
+               return gtk_text_iter_compare (&begin, iter) <= 0 &&
+                      gtk_text_iter_compare (iter, &end) <= 0;
+       }
 
-       return gtk_text_iter_compare (iter, &begin) >= 0 &&
-              gtk_text_iter_compare (iter, &end) <= 0;
+       return FALSE;
 }


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