[gtksourceview/wip/search] search: count the number of occurrences (not finished)



commit 8222036982febcb33dc69907665faa40016c8152
Author: Sébastien Wilmet <swilmet gnome org>
Date:   Fri Jun 21 22:44:50 2013 +0200

    search: count the number of occurrences (not finished)
    
    What is missing is the udpate of occurrences_count on text deletion. But
    it's difficult to say if it's correct for the text insertion. There are
    most probably corner cases where it doesn't work. Next step: write unit
    tests.

 gtksourceview/gtksourcesearch.c |  188 +++++++++++++++++++++++++++++++-------
 tests/test-search.c             |   23 +++++
 tests/test-search.ui            |   13 +++
 3 files changed, 189 insertions(+), 35 deletions(-)
---
diff --git a/gtksourceview/gtksourcesearch.c b/gtksourceview/gtksourcesearch.c
index 7253b9a..bdf15bb 100644
--- a/gtksourceview/gtksourcesearch.c
+++ b/gtksourceview/gtksourcesearch.c
@@ -92,9 +92,9 @@
  * Example where forward_to_tag_toggle() is really nice:
  * <occurrence> [1 GB of text] <next-occurrence>
  * Once the buffer has been scanned once, switching between the occurrences is
- * almost instantaneous.
+ * almost instantaneous (O(1) complexity).
  *
- * If the code is too complicated and contains strange bugs, you have two
+ * If the code seems too complicated and contains strange bugs, you have two
  * choices:
  * - Write more unit tests, understand correctly the code and fix it.
  * - Rewrite the code to implement the simpler solution explained above :-)
@@ -109,8 +109,13 @@ struct _GtkSourceSearchPrivate
        gint text_nb_lines;
        GtkTextSearchFlags flags;
 
-       /* The region to scan and highlight. If NULL, the scan is finished. */
-       GtkTextRegion *region;
+       /* The region not already scanned since the last search state update. */
+       GtkTextRegion *region_not_scanned;
+
+       /* The region to scan and highlight. If NULL, the scan is finished. It
+        * can contains regions already scanned, but must be rescanned.
+        */
+       GtkTextRegion *region_to_scan;
 
        /* The region to scan and highlight in priority. I.e. the visible part
         * of the buffer on the screen.
@@ -267,10 +272,16 @@ get_first_subregion (GtkTextRegion *region,
 static void
 clear_search (GtkSourceSearch *search)
 {
-       if (search->priv->region != NULL)
+       if (search->priv->region_not_scanned != NULL)
+       {
+               gtk_text_region_destroy (search->priv->region_not_scanned, TRUE);
+               search->priv->region_not_scanned = NULL;
+       }
+
+       if (search->priv->region_to_scan != NULL)
        {
-               gtk_text_region_destroy (search->priv->region, TRUE);
-               search->priv->region = NULL;
+               gtk_text_region_destroy (search->priv->region_to_scan, TRUE);
+               search->priv->region_to_scan = NULL;
        }
 
        if (search->priv->high_priority_region != NULL)
@@ -322,9 +333,9 @@ adjust_subregion (GtkSourceSearch *search,
 
        if (gtk_text_iter_has_tag (start, search->priv->found_tag))
        {
-               if (search->priv->region != NULL)
+               if (search->priv->region_to_scan != NULL)
                {
-                       GtkTextRegion *region = gtk_text_region_intersect (search->priv->region,
+                       GtkTextRegion *region = gtk_text_region_intersect (search->priv->region_to_scan,
                                                                           start,
                                                                           &initial_start);
 
@@ -363,9 +374,9 @@ adjust_subregion (GtkSourceSearch *search,
 
        if (gtk_text_iter_has_tag (end, search->priv->found_tag))
        {
-               if (search->priv->region != NULL)
+               if (search->priv->region_to_scan != NULL)
                {
-                       GtkTextRegion *region = gtk_text_region_intersect (search->priv->region,
+                       GtkTextRegion *region = gtk_text_region_intersect (search->priv->region_to_scan,
                                                                           &initial_end,
                                                                           end);
 
@@ -402,9 +413,9 @@ adjust_subregion (GtkSourceSearch *search,
 }
 
 static void
-highlight_subregion (GtkSourceSearch *search,
-                    GtkTextIter     *start,
-                    GtkTextIter     *end)
+scan_subregion (GtkSourceSearch *search,
+               GtkTextIter     *start,
+               GtkTextIter     *end)
 {
        GtkTextIter iter;
        GtkTextIter *limit;
@@ -427,9 +438,14 @@ highlight_subregion (GtkSourceSearch *search,
                                    start,
                                    end);
 
-       if (search->priv->region != NULL)
+       if (search->priv->region_not_scanned != NULL)
+       {
+               gtk_text_region_subtract (search->priv->region_not_scanned, start, end);
+       }
+
+       if (search->priv->region_to_scan != NULL)
        {
-               gtk_text_region_subtract (search->priv->region, start, end);
+               gtk_text_region_subtract (search->priv->region_to_scan, start, end);
        }
 
        if (search->priv->text == NULL)
@@ -467,6 +483,8 @@ highlight_subregion (GtkSourceSearch *search,
                                                   search->priv->found_tag,
                                                   &match_start,
                                                   &match_end);
+
+                       search->priv->occurrences_count++;
                }
 
                iter = match_end;
@@ -475,8 +493,8 @@ highlight_subregion (GtkSourceSearch *search,
 }
 
 static void
-highlight_all_region (GtkSourceSearch *search,
-                     GtkTextRegion   *region_to_highlight)
+scan_all_region (GtkSourceSearch *search,
+                GtkTextRegion   *region_to_highlight)
 {
        gint nb_subregions = gtk_text_region_subregions (region_to_highlight);
        GtkTextIter start_search;
@@ -499,7 +517,7 @@ highlight_all_region (GtkSourceSearch *search,
 
        gtk_text_iter_order (&start_search, &end_search);
 
-       highlight_subregion (search, &start_search, &end_search);
+       scan_subregion (search, &start_search, &end_search);
 }
 
 /* Scan a chunk of the region. If the region is small enough, all the region
@@ -507,14 +525,14 @@ highlight_all_region (GtkSourceSearch *search,
  * block the UI normally.
  */
 static void
-highlight_chunk_region (GtkSourceSearch *search)
+scan_chunk_region (GtkSourceSearch *search)
 {
        gint nb_remaining_lines = SCAN_BATCH_SIZE;
        GtkTextIter start;
        GtkTextIter end;
 
        while (nb_remaining_lines > 0 &&
-              get_first_subregion (search->priv->region, &start, &end))
+              get_first_subregion (search->priv->region_to_scan, &start, &end))
        {
                GtkTextIter limit = start;
                gint start_line;
@@ -527,7 +545,7 @@ highlight_chunk_region (GtkSourceSearch *search)
                        limit = end;
                }
 
-               highlight_subregion (search, &start, &limit);
+               scan_subregion (search, &start, &limit);
 
                start_line = gtk_text_iter_get_line (&start);
                limit_line = gtk_text_iter_get_line (&limit);
@@ -547,28 +565,37 @@ idle_scan_cb (GtkSourceSearch *search)
                 * is the visible area on the screen. So we can highlight it in
                 * one batch.
                 */
-               highlight_all_region (search, search->priv->high_priority_region);
+               scan_all_region (search, search->priv->high_priority_region);
 
                gtk_text_region_destroy (search->priv->high_priority_region, TRUE);
                search->priv->high_priority_region = NULL;
        }
        else
        {
-               highlight_chunk_region (search);
+               scan_chunk_region (search);
        }
 
-       if (is_text_region_empty (search->priv->region))
+       if (is_text_region_empty (search->priv->region_to_scan))
        {
                finished = TRUE;
                search->priv->idle_scan_id = 0;
 
-               if (search->priv->region != NULL)
+               g_object_notify (G_OBJECT (search->priv->buffer), "search-occurrences-count");
+
+               if (search->priv->region_to_scan != NULL)
                {
-                       gtk_text_region_destroy (search->priv->region, TRUE);
-                       search->priv->region = NULL;
+                       gtk_text_region_destroy (search->priv->region_to_scan, TRUE);
+                       search->priv->region_to_scan = NULL;
                }
        }
 
+       if (is_text_region_empty (search->priv->region_not_scanned) &&
+           search->priv->region_not_scanned != NULL)
+       {
+               gtk_text_region_destroy (search->priv->region_not_scanned, TRUE);
+               search->priv->region_not_scanned = NULL;
+       }
+
        return !finished;
 }
 
@@ -589,7 +616,7 @@ add_subregion_to_scan (GtkSourceSearch   *search,
        GtkTextIter start = *subregion_start;
        GtkTextIter end = *subregion_end;
 
-       if (search->priv->region == NULL)
+       if (search->priv->region_to_scan == NULL)
        {
                return;
        }
@@ -604,7 +631,7 @@ add_subregion_to_scan (GtkSourceSearch   *search,
                gtk_text_iter_forward_to_line_end (&end);
        }
 
-       gtk_text_region_add (search->priv->region, &start, &end);
+       gtk_text_region_add (search->priv->region_to_scan, &start, &end);
 
        install_idle_scan (search);
 
@@ -630,13 +657,69 @@ update (GtkSourceSearch *search)
 
        clear_search (search);
 
-       search->priv->region = gtk_text_region_new (search->priv->buffer);
+       search->priv->region_not_scanned = gtk_text_region_new (search->priv->buffer);
+       search->priv->region_to_scan = gtk_text_region_new (search->priv->buffer);
 
        gtk_text_buffer_get_bounds (search->priv->buffer, &start, &end);
+
+       gtk_text_region_add (search->priv->region_not_scanned, &start, &end);
        add_subregion_to_scan (search, &start, &end);
 }
 
 static void
+insert_text_before_cb (GtkSourceSearch *search,
+                      GtkTextIter     *location,
+                      gchar           *text,
+                      gint             length)
+{
+       GtkTextIter start = *location;
+       GtkTextIter end = *location;
+       GtkTextIter iter;
+       GtkTextIter match_start;
+       GtkTextIter match_end;
+
+       if (search->priv->text == NULL)
+       {
+               return;
+       }
+
+       adjust_subregion (search, &start, &end);
+
+       iter = start;
+
+       while (gtk_text_iter_forward_search (&iter,
+                                            search->priv->text,
+                                            search->priv->flags,
+                                            &match_start,
+                                            &match_end,
+                                            &end))
+       {
+               if (search->priv->region_not_scanned == NULL)
+               {
+                       search->priv->occurrences_count--;
+               }
+               else
+               {
+                       GtkTextRegion *region = gtk_text_region_intersect (search->priv->region_not_scanned,
+                                                                          &match_start,
+                                                                          &match_end);
+
+                       if (is_text_region_empty (region))
+                       {
+                               search->priv->occurrences_count--;
+                       }
+
+                       if (region != NULL)
+                       {
+                               gtk_text_region_destroy (region, TRUE);
+                       }
+               }
+
+               iter = match_end;
+       }
+}
+
+static void
 insert_text_after_cb (GtkSourceSearch *search,
                      GtkTextIter     *location,
                      gchar           *text,
@@ -654,6 +737,24 @@ insert_text_after_cb (GtkSourceSearch *search,
 }
 
 static void
+delete_range_before_cb (GtkSourceSearch *search,
+                       GtkTextIter     *start,
+                       GtkTextIter     *end)
+{
+       GtkTextIter start_buffer;
+       GtkTextIter end_buffer;
+
+       gtk_text_buffer_get_bounds (search->priv->buffer, &start_buffer, &end_buffer);
+
+       if (gtk_text_iter_equal (start, &start_buffer) &&
+           gtk_text_iter_equal (end, &end_buffer))
+       {
+               /* Special case when removing all the text. */
+               search->priv->occurrences_count = 0;
+       }
+}
+
+static void
 delete_range_after_cb (GtkSourceSearch *search,
                       GtkTextIter     *start,
                       GtkTextIter     *end)
@@ -672,12 +773,24 @@ set_buffer (GtkSourceSearch *search,
 
        g_signal_connect_object (buffer,
                                 "insert-text",
+                                G_CALLBACK (insert_text_before_cb),
+                                search,
+                                G_CONNECT_SWAPPED);
+
+       g_signal_connect_object (buffer,
+                                "insert-text",
                                 G_CALLBACK (insert_text_after_cb),
                                 search,
                                 G_CONNECT_AFTER | G_CONNECT_SWAPPED);
 
        g_signal_connect_object (buffer,
                                 "delete-range",
+                                G_CALLBACK (delete_range_before_cb),
+                                search,
+                                G_CONNECT_SWAPPED);
+
+       g_signal_connect_object (buffer,
+                                "delete-range",
                                 G_CALLBACK (delete_range_after_cb),
                                 search,
                                 G_CONNECT_AFTER | G_CONNECT_SWAPPED);
@@ -834,23 +947,28 @@ _gtk_source_search_update_highlight (GtkSourceSearch   *search,
        g_return_if_fail (end != NULL);
 
        if (dispose_has_run (search) ||
-           is_text_region_empty (search->priv->region))
+           is_text_region_empty (search->priv->region_to_scan))
        {
                return;
        }
 
-       region_to_highlight = gtk_text_region_intersect (search->priv->region,
+       region_to_highlight = gtk_text_region_intersect (search->priv->region_to_scan,
                                                         start,
                                                         end);
 
-       if (region_to_highlight == NULL)
+       if (is_text_region_empty (region_to_highlight))
        {
+               if (region_to_highlight != NULL)
+               {
+                       gtk_text_region_destroy (region_to_highlight, TRUE);
+               }
+
                return;
        }
 
        if (synchronous)
        {
-               highlight_all_region (search, region_to_highlight);
+               scan_all_region (search, region_to_highlight);
                gtk_text_region_destroy (region_to_highlight, TRUE);
        }
        else
diff --git a/tests/test-search.c b/tests/test-search.c
index 3f10245..8cc11a4 100644
--- a/tests/test-search.c
+++ b/tests/test-search.c
@@ -46,6 +46,22 @@ open_file (GtkSourceBuffer *buffer,
 }
 
 static void
+on_occurrences_count_notify_cb (GtkSourceBuffer *buffer,
+                               GParamSpec      *spec,
+                               GtkLabel        *label)
+{
+       guint occurrences_count;
+       gchar *text;
+
+       g_object_get (buffer, "search-occurrences-count", &occurrences_count, NULL);
+
+       text = g_strdup_printf ("%u occurrences", occurrences_count);
+
+       gtk_label_set_text (label, text);
+       g_free (text);
+}
+
+static void
 create_window (void)
 {
        GtkBuilder *builder;
@@ -54,6 +70,7 @@ create_window (void)
        GtkSourceView *source_view;
        GtkSourceBuffer *source_buffer;
        GtkSearchEntry *search_entry;
+       GtkLabel *label_occurrences_count;
        PangoFontDescription *font;
 
        builder = gtk_builder_new ();
@@ -70,6 +87,7 @@ create_window (void)
        window = GTK_WINDOW (gtk_builder_get_object (builder, "window"));
        source_view = GTK_SOURCE_VIEW (gtk_builder_get_object (builder, "source_view"));
        search_entry = GTK_SEARCH_ENTRY (gtk_builder_get_object (builder, "search_entry"));
+       label_occurrences_count = GTK_LABEL (gtk_builder_get_object (builder, "label_occurrences_count"));
 
        font = pango_font_description_from_string ("Monospace 10");
        gtk_widget_override_font (GTK_WIDGET (source_view), font);
@@ -97,6 +115,11 @@ create_window (void)
                                source_buffer, "search-text",
                                G_BINDING_DEFAULT);
 
+       g_signal_connect (source_buffer,
+                         "notify::search-occurrences-count",
+                         G_CALLBACK (on_occurrences_count_notify_cb),
+                         label_occurrences_count);
+
        g_object_unref (builder);
 }
 
diff --git a/tests/test-search.ui b/tests/test-search.ui
index b175151..3b52dab 100644
--- a/tests/test-search.ui
+++ b/tests/test-search.ui
@@ -77,6 +77,19 @@
                 <property name="height">1</property>
               </packing>
             </child>
+            <child>
+              <object class="GtkLabel" id="label_occurrences_count">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label">0 occurrences</property>
+              </object>
+              <packing>
+                <property name="left_attach">2</property>
+                <property name="top_attach">0</property>
+                <property name="width">1</property>
+                <property name="height">1</property>
+              </packing>
+            </child>
           </object>
           <packing>
             <property name="left_attach">0</property>


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