[gnome-builder/wip/slaf/spellcheck-sidebar: 5/33] spellchecker: count words in an idle



commit fd8294252c26c1c7c03a3232a4112558f9cbedd6
Author: Sébastien Lafargue <slafargue gnome org>
Date:   Sun Dec 18 00:04:00 2016 +0100

    spellchecker: count words in an idle
    
    For long texts it can take a significative
    time to count the occurrence of each word.
    
    We use GtkSourceRegion in an idle to do it
    by chunks.

 libide/editor/ide-editor-spell-navigator.c |  205 +++++++++++++++++++++-------
 libide/editor/ide-editor-spell-navigator.h |    7 +-
 libide/editor/ide-editor-spell-widget.c    |   53 ++++++--
 3 files changed, 197 insertions(+), 68 deletions(-)
---
diff --git a/libide/editor/ide-editor-spell-navigator.c b/libide/editor/ide-editor-spell-navigator.c
index e4a5fdd..8282dfd 100644
--- a/libide/editor/ide-editor-spell-navigator.c
+++ b/libide/editor/ide-editor-spell-navigator.c
@@ -25,17 +25,22 @@
 #include "ide-editor-spell-navigator.h"
 #include "ide-editor-spell-utils.h"
 
+#define SPELLCHECKER_SUBREGION_LENGTH 500
+
 struct _IdeEditorSpellNavigator
 {
-  GObject        parent_instance;
-
-  GtkTextView   *view;
-  GtkTextBuffer *buffer;
-  GHashTable    *words_count;
-  GtkTextMark   *start_boundary;
-  GtkTextMark   *end_boundary;
-  GtkTextMark   *word_start;
-  GtkTextMark   *word_end;
+  GObject          parent_instance;
+
+  GtkTextView     *view;
+  GtkTextBuffer   *buffer;
+
+  GHashTable      *words_count;
+  GtkTextMark     *start_boundary;
+  GtkTextMark     *end_boundary;
+  GtkTextMark     *word_start;
+  GtkTextMark     *word_end;
+
+  guint            words_counted : 1;
 };
 
 static void gspell_navigator_iface_init (gpointer g_iface, gpointer iface_data);
@@ -46,12 +51,89 @@ G_DEFINE_TYPE_EXTENDED (IdeEditorSpellNavigator, ide_editor_spell_navigator, G_T
 enum {
   PROP_0,
   PROP_VIEW,
+  PROP_WORDS_COUNTED,
   N_PROPS
 };
 
 static GParamSpec *properties [N_PROPS];
 
-/* TODO: do it async */
+typedef struct
+{
+  IdeEditorSpellNavigator *navigator;
+  GtkSourceRegion         *words_count_region;
+  GtkSourceRegionIter      iter;
+} WordsCountState;
+
+static void
+words_count_state_free (gpointer *user_data)
+{
+  WordsCountState *state = (WordsCountState *)user_data;
+
+  g_object_unref (state->words_count_region);
+
+  g_slice_free (WordsCountState, state);
+}
+
+static gboolean
+ide_editor_spell_navigator_words_count_cb (WordsCountState *state)
+{
+  IdeEditorSpellNavigator *self = state->navigator;
+  GtkTextTag *no_spell_check_tag;
+  GtkTextIter start;
+  GtkTextIter end;
+  GtkTextIter word_start;
+  GtkTextIter word_end;
+  gchar *word;
+  guint count;
+
+  g_assert (IDE_IS_EDITOR_SPELL_NAVIGATOR (self));
+
+  no_spell_check_tag = ide_editor_spell_utils_get_no_spell_check_tag (self->buffer);
+  if (gtk_source_region_iter_get_subregion (&state->iter, &start, &end))
+  {
+    word_start = word_end = start;
+    while (TRUE)
+      {
+        if (!ide_editor_spell_utils_text_iter_starts_word (&word_start))
+          {
+            GtkTextIter iter;
+
+            iter = word_start;
+            ide_editor_spell_utils_text_iter_forward_word_end (&word_start);
+            if (gtk_text_iter_equal (&iter, &word_start))
+              break;
+
+            ide_editor_spell_utils_text_iter_backward_word_start (&word_start);
+          }
+
+        if (!ide_editor_spell_utils_skip_no_spell_check (no_spell_check_tag, &word_start, &end))
+          break;
+
+        word_end = word_start;
+        ide_editor_spell_utils_text_iter_forward_word_end (&word_end);
+        if (gtk_text_iter_compare (&word_end, &end) >= 0)
+          break;
+
+        word = gtk_text_buffer_get_text (self->buffer, &word_start, &word_end, FALSE);
+        if ((count = GPOINTER_TO_UINT (g_hash_table_lookup (self->words_count, word))))
+          count++;
+        else
+          count = 1;
+
+        g_hash_table_insert (self->words_count, word, GUINT_TO_POINTER (count));
+
+        word_start = word_end;
+      }
+
+    if (gtk_source_region_iter_next (&state->iter))
+      return G_SOURCE_CONTINUE;
+  }
+
+  self->words_counted = TRUE;
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_WORDS_COUNTED]);
+
+  return G_SOURCE_REMOVE;
+}
 
 /* Always process start and end by init_boudaries before */
 static GHashTable *
@@ -60,54 +142,61 @@ ide_editor_spell_navigator_count_words (IdeEditorSpellNavigator *self,
                                         GtkTextIter             *end)
 {
   GHashTable *table;
-  GtkTextTag *no_spell_check_tag;
-  GtkTextIter word_start;
-  GtkTextIter word_end;
-  guint count;
-  gchar *word;
+  GtkSourceRegion *words_count_region;
+  WordsCountState *state;
+  GtkTextIter start_subregion;
+  GtkTextIter end_subregion;
+  gint line_start;
+  gint line_end;
+  gint nb_subregion;
 
   g_assert (IDE_IS_EDITOR_SPELL_NAVIGATOR (self));
   g_assert (start != NULL);
   g_assert (end != NULL);
 
-  word_start = word_end = *start;
-  no_spell_check_tag = ide_editor_spell_utils_get_no_spell_check_tag (self->buffer);
+  words_count_region = gtk_source_region_new (self->buffer);
+  line_start = gtk_text_iter_get_line (start);
+  line_end = gtk_text_iter_get_line (end);
+  nb_subregion = (line_end - line_start + 1) / SPELLCHECKER_SUBREGION_LENGTH;
 
-  table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
-  while (TRUE)
+  if (nb_subregion > 1)
     {
-      if (!ide_editor_spell_utils_text_iter_starts_word (&word_start))
+      for (gint i = 0; i < nb_subregion; ++i)
         {
-          GtkTextIter iter;
-
-          iter = word_start;
-          ide_editor_spell_utils_text_iter_forward_word_end (&word_start);
-          if (gtk_text_iter_equal (&iter, &word_start))
-            break;
-
-          ide_editor_spell_utils_text_iter_backward_word_start (&word_start);
+          line_end = line_start + SPELLCHECKER_SUBREGION_LENGTH - 1;
+          gtk_text_buffer_get_iter_at_line_offset (self->buffer, &start_subregion, line_start, 0);
+          gtk_text_buffer_get_iter_at_line_offset (self->buffer, &end_subregion, line_end, 0);
+          if (!gtk_text_iter_ends_line (&end_subregion))
+            gtk_text_iter_forward_to_line_end (&end_subregion);
+
+          gtk_source_region_add_subregion (words_count_region, &start_subregion, &end_subregion);
+          line_start = line_end + 1;
         }
+    }
 
-      if (!ide_editor_spell_utils_skip_no_spell_check (no_spell_check_tag, &word_start, end))
-        break;
+  gtk_text_buffer_get_iter_at_line_offset (self->buffer, &start_subregion, line_start, 0);
+  gtk_source_region_add_subregion (words_count_region, &start_subregion, end);
 
-      word_end = word_start;
-      ide_editor_spell_utils_text_iter_forward_word_end (&word_end);
-      if (gtk_text_iter_compare (&word_end, end) >= 0)
-        break;
+  table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+  state = g_slice_new (WordsCountState);
+  state->navigator = self;
+  state->words_count_region = words_count_region;
+  gtk_source_region_get_start_region_iter (words_count_region, &state->iter);
 
-      word = gtk_text_buffer_get_text (self->buffer, &word_start, &word_end, FALSE);
-      if ((count = GPOINTER_TO_UINT (g_hash_table_lookup (table, word))))
-        count++;
-      else
-        count = 1;
+  g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
+                   (GSourceFunc)ide_editor_spell_navigator_words_count_cb,
+                   state,
+                   (GDestroyNotify)words_count_state_free);
 
-      g_hash_table_insert (table, word, GUINT_TO_POINTER (count));
+  return table;
+}
 
-      word_start = word_end;
-    }
+gboolean
+ide_editor_spell_navigator_get_is_words_counted (IdeEditorSpellNavigator *self)
+{
+  g_assert (IDE_IS_EDITOR_SPELL_NAVIGATOR (self));
 
-  return table;
+  return self->words_counted;
 }
 
 guint
@@ -116,7 +205,7 @@ ide_editor_spell_navigator_get_count (IdeEditorSpellNavigator *self,
 {
   g_assert (IDE_IS_EDITOR_SPELL_NAVIGATOR (self));
 
-  if (ide_str_empty0 (word))
+  if (self->words_count == NULL || ide_str_empty0 (word))
     return 0;
   else
     return GPOINTER_TO_UINT (g_hash_table_lookup (self->words_count, word));
@@ -206,16 +295,19 @@ set_view (IdeEditorSpellNavigator *self,
   g_assert (self->view == NULL);
   g_assert (self->buffer == NULL);
 
-  self->view = g_object_ref (view);
-  self->buffer = g_object_ref (gtk_text_view_get_buffer (view));
+  if (view != self->view)
+    {
+      self->view = g_object_ref (view);
+      self->buffer = g_object_ref (gtk_text_view_get_buffer (view));
 
-  init_boundaries (self);
+      init_boundaries (self);
 
-  gtk_text_buffer_get_iter_at_mark (self->buffer, &start, self->start_boundary);
-  gtk_text_buffer_get_iter_at_mark (self->buffer, &end, self->end_boundary);
-  self->words_count = ide_editor_spell_navigator_count_words (self, &start, &end);
+      gtk_text_buffer_get_iter_at_mark (self->buffer, &start, self->start_boundary);
+      gtk_text_buffer_get_iter_at_mark (self->buffer, &end, self->end_boundary);
+      self->words_count = ide_editor_spell_navigator_count_words (self, &start, &end);
 
-  g_object_notify (G_OBJECT (self), "view");
+      g_object_notify (G_OBJECT (self), "view");
+    }
 }
 
 static void
@@ -251,6 +343,10 @@ ide_editor_spell_navigator_get_property (GObject    *object,
       g_value_set_object (value, self->view);
       break;
 
+    case PROP_WORDS_COUNTED:
+      g_value_set_boolean (value, self->words_counted);
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
@@ -274,6 +370,13 @@ ide_editor_spell_navigator_class_init (IdeEditorSpellNavigatorClass *klass)
                         G_PARAM_CONSTRUCT_ONLY |
                         G_PARAM_STATIC_STRINGS);
 
+  properties [PROP_WORDS_COUNTED] =
+    g_param_spec_boolean ("words-counted",
+                          "words-counted",
+                          "words-counted",
+                          FALSE,
+                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
   g_object_class_install_properties (object_class, N_PROPS, properties);
 }
 
diff --git a/libide/editor/ide-editor-spell-navigator.h b/libide/editor/ide-editor-spell-navigator.h
index 34c7513..4081832 100644
--- a/libide/editor/ide-editor-spell-navigator.h
+++ b/libide/editor/ide-editor-spell-navigator.h
@@ -28,9 +28,10 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (IdeEditorSpellNavigator, ide_editor_spell_navigator, IDE, EDITOR_SPELL_NAVIGATOR, 
GInitiallyUnowned)
 
-GspellNavigator *ide_editor_spell_navigator_new          (GtkTextView             *view);
-guint            ide_editor_spell_navigator_get_count    (IdeEditorSpellNavigator *self,
-                                                          const gchar             *word);
+GspellNavigator *ide_editor_spell_navigator_new                   (GtkTextView             *view);
+guint            ide_editor_spell_navigator_get_count             (IdeEditorSpellNavigator *self,
+                                                                   const gchar             *word);
+gboolean         ide_editor_spell_navigator_get_is_words_counted  (IdeEditorSpellNavigator *self);
 
 G_END_DECLS
 
diff --git a/libide/editor/ide-editor-spell-widget.c b/libide/editor/ide-editor-spell-widget.c
index d4b5cad..8e190fd 100644
--- a/libide/editor/ide-editor-spell-widget.c
+++ b/libide/editor/ide-editor-spell-widget.c
@@ -154,6 +154,28 @@ fill_suggestions_box (IdeEditorSpellWidget *self,
     }
 }
 
+static void
+update_count_label (IdeEditorSpellWidget *self)
+{
+  const gchar *word;
+  guint count;
+
+  word = gtk_label_get_text (self->word_label);
+  if (0 != (count = ide_editor_spell_navigator_get_count (IDE_EDITOR_SPELL_NAVIGATOR (self->navigator), 
word)))
+    {
+      g_autofree gchar *count_text = NULL;
+
+      if (count > 1000)
+        count_text = g_strdup ("(>1000)");
+      else
+        count_text = g_strdup_printf ("(%i)", count);
+
+      gtk_label_set_text (self->count_label, count_text);
+    }
+  else
+    gtk_label_set_text (self->count_label, "");
+}
+
 static gboolean
 jump_to_next_misspelled_word (IdeEditorSpellWidget *self)
 {
@@ -161,7 +183,7 @@ jump_to_next_misspelled_word (IdeEditorSpellWidget *self)
   g_autofree gchar *word = NULL;
   g_autofree gchar *first_result = NULL;
   GtkListBoxRow *row;
-  guint count;
+
   GError *error = NULL;
   gboolean ret = FALSE;
 
@@ -170,19 +192,9 @@ jump_to_next_misspelled_word (IdeEditorSpellWidget *self)
   gtk_widget_grab_focus (GTK_WIDGET (self->word_entry));
   if ((ret = gspell_navigator_goto_next (self->navigator, &word, &checker, &error)))
     {
-      if (0 != (count = ide_editor_spell_navigator_get_count (IDE_EDITOR_SPELL_NAVIGATOR (self->navigator), 
word)))
-        {
-          g_autofree gchar *count_text = NULL;
-
-          if (count > 1000)
-            count_text = g_strdup ("(>1000)");
-          else
-            count_text = g_strdup_printf ("(%i)", count);
-
-          gtk_label_set_text (self->count_label, count_text);
-        }
-
       gtk_label_set_text (self->word_label, word);
+      update_count_label (self);
+
       fill_suggestions_box (self, word, &first_result);
       if (!ide_str_empty0 (first_result))
         {
@@ -482,6 +494,16 @@ ide_editor_spell_widget__language_notify_cb (IdeEditorSpellWidget *self,
 }
 
 static void
+ide_editor_spell_widget_words_counted_cb (IdeEditorSpellWidget *self,
+                                          GParamSpec           *pspec,
+                                          GspellNavigator      *navigator)
+{
+  g_assert (IDE_IS_EDITOR_SPELL_WIDGET (self));
+
+  update_count_label (self);
+}
+
+static void
 ide_editor_spell_widget_constructed (GObject *object)
 {
   IdeEditorSpellWidget *self = (IdeEditorSpellWidget *)object;
@@ -499,7 +521,10 @@ ide_editor_spell_widget_constructed (GObject *object)
   gspell_language_chooser_set_language (GSPELL_LANGUAGE_CHOOSER (self->language_chooser_button),
                                         self->spellchecker_language);
 
-  self->navigator = ide_editor_spell_navigator_new (GTK_TEXT_VIEW (self->view));
+  g_signal_connect_swapped (self->navigator,
+                            "notify::words-counted",
+                            G_CALLBACK (ide_editor_spell_widget_words_counted_cb),
+                            self);
 
   g_signal_connect_swapped (self->word_entry,
                             "changed",


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