[gnome-builder/wip/chergert/layout] spellcheck: bring back dictionary functionality



commit 78f2b91761085b80baed5cd2ac1fa28e82362c12
Author: Christian Hergert <chergert redhat com>
Date:   Sat Jul 15 19:30:03 2017 -0700

    spellcheck: bring back dictionary functionality
    
    This adds back the IdeEditorSpellDict as a new class type
    GbpSpellDict. It also changes a few things to be more
    defensive and fixes a leak in finalize, where it missed chaining
    up to the parent finalize.

 plugins/spellcheck/gbp-spell-dict.c |  571 +++++++++++++++++++++++++++++++++++
 plugins/spellcheck/gbp-spell-dict.h |   42 +++
 plugins/spellcheck/meson.build      |    2 +
 3 files changed, 615 insertions(+), 0 deletions(-)
---
diff --git a/plugins/spellcheck/gbp-spell-dict.c b/plugins/spellcheck/gbp-spell-dict.c
new file mode 100644
index 0000000..3d1f2d8
--- /dev/null
+++ b/plugins/spellcheck/gbp-spell-dict.c
@@ -0,0 +1,571 @@
+/* gbp-spell-dict.c
+ *
+ * Copyright (C) 2016 Sébastien Lafargue <slafargue gnome org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* This GObject exists until Gspell handles managing the content of a dictionary */
+
+#include "gbp-spell-dict.h"
+#include <enchant.h>
+#include <gspell/gspell.h>
+
+#include <ide.h>
+
+typedef enum {
+  INIT_NONE,
+  INIT_PROCESSING,
+  INIT_DONE
+} InitStatus;
+
+struct _GbpSpellDict
+{
+  GObject               parent_instance;
+
+  GspellChecker        *checker;
+  EnchantBroker        *broker;
+  EnchantDict          *dict;
+  const GspellLanguage *language;
+
+  GHashTable           *words;
+
+  InitStatus            init_status;
+  guint                 update_needed : 1;
+};
+
+G_DEFINE_TYPE (GbpSpellDict, gbp_spell_dict, G_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_CHECKER,
+  N_PROPS
+};
+
+enum {
+  LOADED,
+  LAST_SIGNAL
+};
+
+static GParamSpec *properties [N_PROPS];
+static guint signals [LAST_SIGNAL];
+
+static void read_line_cb (GObject      *object,
+                          GAsyncResult *result,
+                          gpointer      user_data);
+
+typedef struct
+{
+  GbpSpellDict     *self;
+  GFile            *file;
+  GDataInputStream *data_stream;
+  GHashTable       *hash_table;
+} TaskState;
+
+static void
+task_state_free (gpointer data)
+{
+  TaskState *state = (TaskState *)data;
+
+  g_assert (state != NULL);
+
+  g_clear_object (&state->file);
+  g_clear_pointer (&state->hash_table, g_hash_table_unref);
+  g_slice_free (TaskState, state);
+}
+
+static void
+read_line_async (GTask *task)
+{
+  TaskState *state;
+
+  g_assert (G_IS_TASK (task));
+
+  state = g_task_get_task_data (task);
+  g_assert (state != NULL);
+  g_assert (state->hash_table != NULL);
+  g_assert (GBP_IS_SPELL_DICT (state->self));
+  g_assert (G_IS_DATA_INPUT_STREAM (state->data_stream));
+
+  g_data_input_stream_read_line_async (state->data_stream,
+                                       g_task_get_priority (task),
+                                       g_task_get_cancellable (task),
+                                       read_line_cb,
+                                       task);
+}
+
+static void
+read_line_cb (GObject      *object,
+              GAsyncResult *result,
+              gpointer      user_data)
+{
+  g_autoptr (GTask) task = (GTask *)user_data;
+  g_autoptr(GError) error = NULL;
+  TaskState *state;
+  gchar *word;
+  gsize len;
+
+  if (g_task_return_error_if_cancelled (task))
+    return;
+
+  state = g_task_get_task_data (task);
+  if (NULL == (word = g_data_input_stream_read_line_finish_utf8 (state->data_stream,
+                                                                 result,
+                                                                 &len,
+                                                                 &error)))
+    {
+      if (error != NULL)
+        {
+          /* TODO: check invalid utf8 string to skip it */
+          g_task_return_error (task, g_steal_pointer (&error));
+        }
+      else
+        g_task_return_pointer (task, state->hash_table, (GDestroyNotify)g_hash_table_unref);
+    }
+  else
+    {
+      if (len > 0)
+        g_hash_table_add (state->hash_table, word);
+
+      read_line_async (g_steal_pointer (&task));
+    }
+}
+
+static void
+open_file_cb (GObject      *object,
+              GAsyncResult *result,
+              gpointer      user_data)
+{
+  g_autoptr (GTask) task = (GTask *)user_data;
+  g_autoptr(GError) error = NULL;
+  GFileInputStream *stream;
+  TaskState *state;
+
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_TASK (task));
+
+  if (g_task_return_error_if_cancelled (task))
+    return;
+
+  state = g_task_get_task_data (task);
+  if (NULL == (stream = g_file_read_finish (state->file, result, &error)))
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  state->data_stream = g_data_input_stream_new (G_INPUT_STREAM (stream));
+  read_line_async (g_steal_pointer (&task));
+}
+
+gboolean
+gbp_spell_dict_personal_contains (GbpSpellDict *self,
+                                  const gchar  *word)
+{
+  g_assert (GBP_IS_SPELL_DICT (self));
+
+  if (ide_str_empty0 (word))
+    return FALSE;
+
+  if (self->dict != NULL)
+    {
+      if (self->words == NULL)
+        return FALSE;
+
+      return (NULL != g_hash_table_lookup (self->words, word));
+    }
+  else
+    {
+      g_warning ("No dictionaries loaded");
+      return FALSE;
+    }
+}
+
+gboolean
+gbp_spell_dict_add_word_to_personal (GbpSpellDict *self,
+                                     const gchar  *word)
+{
+  g_assert (GBP_IS_SPELL_DICT (self));
+  g_assert (!ide_str_empty0 (word));
+
+ if (self->dict != NULL)
+    {
+      if (gbp_spell_dict_personal_contains (self, word))
+        return FALSE;
+
+      enchant_dict_add (self->dict, word, -1);
+      g_hash_table_add (self->words, g_strdup (word));
+      return TRUE;
+    }
+  else
+    {
+      g_warning ("No dictionaries loaded");
+      return FALSE;
+    }
+}
+
+gboolean
+gbp_spell_dict_remove_word_from_personal (GbpSpellDict *self,
+                                          const gchar  *word)
+{
+  g_assert (GBP_IS_SPELL_DICT (self));
+  g_assert (!ide_str_empty0 (word));
+
+  if (self->dict != NULL)
+    {
+      if (!gbp_spell_dict_personal_contains (self, word) || self->words == NULL)
+        return FALSE;
+
+      enchant_dict_remove (self->dict, word, -1);
+      g_hash_table_remove (self->words, word);
+      return TRUE;
+    }
+  else
+    {
+      g_warning ("No dictionaries loaded");
+      return FALSE;
+    }
+}
+
+static void
+hash_table_foreach_cb (const gchar *key,
+                       const gchar *value,
+                       GPtrArray   *ar)
+{
+  g_ptr_array_add (ar, g_strdup (key));
+}
+
+GPtrArray *
+gbp_spell_dict_get_words (GbpSpellDict  *self)
+{
+  GPtrArray *array;
+
+  g_assert (GBP_IS_SPELL_DICT (self));
+
+  if (self->init_status == INIT_NONE)
+    {
+      g_warning ("Dict not loaded yet, you need to connect and wait for GbpSpellDict::loaded");
+      return NULL;
+    }
+
+  if (self->words == NULL)
+    return NULL;
+
+  array = g_ptr_array_new_with_free_func (g_free);
+  g_hash_table_foreach (self->words, (GHFunc)hash_table_foreach_cb, array);
+
+  return array;
+}
+
+static void
+gbp_spell_dict_get_words_async (GbpSpellDict        *self,
+                                GAsyncReadyCallback  callback,
+                                GCancellable        *cancellable,
+                                gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+  g_autofree gchar *path = NULL;
+  g_autofree gchar *dict_filename = NULL;
+  TaskState *state;
+  gint priority;
+
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_assert (GBP_IS_SPELL_DICT (self));
+  g_assert (callback != NULL);
+
+  state = g_slice_new0 (TaskState);
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, gbp_spell_dict_get_words_async);
+  g_task_set_task_data (task, state, (GDestroyNotify)task_state_free);
+
+  dict_filename = g_strconcat (gspell_language_get_code (self->language), ".dic", NULL);
+  path = g_build_filename (g_get_user_config_dir (), "enchant", dict_filename, NULL);
+  state->self = self;
+  state->hash_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+  state->file = g_file_new_for_path (path);
+
+  priority = g_task_get_priority (task);
+  g_file_read_async (state->file,
+                     priority,
+                     cancellable,
+                     open_file_cb,
+                     g_steal_pointer (&task));
+}
+
+static GHashTable *
+gbp_spell_dict_get_words_finish (GbpSpellDict  *self,
+                                 GAsyncResult  *result,
+                                 GError       **error)
+{
+  g_assert (GBP_IS_SPELL_DICT (self));
+  g_assert (g_task_is_valid (result, self));
+
+  return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static void
+gbp_spell_dict_get_dict_words_cb (GObject      *object,
+                                  GAsyncResult *result,
+                                  gpointer      user_data)
+{
+  GbpSpellDict  *self = (GbpSpellDict *)user_data;
+  g_autoptr(GError) error = NULL;
+  GHashTable *words;
+
+  g_assert (GBP_IS_SPELL_DICT (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (NULL == (words = gbp_spell_dict_get_words_finish (self, result, &error)))
+    {
+      g_debug ("error: %s\n", error->message);
+      self->init_status = INIT_DONE;
+    }
+  else
+    {
+      g_clear_pointer (&self->words, g_hash_table_unref);
+      self->words = g_hash_table_ref (words);
+      self->init_status = INIT_DONE;
+
+      g_signal_emit (self, signals [LOADED], 0);
+    }
+
+  if (self->update_needed == TRUE)
+    {
+      self->update_needed = FALSE;
+      self->init_status = INIT_PROCESSING;
+      gbp_spell_dict_get_words_async (self,
+                                      gbp_spell_dict_get_dict_words_cb,
+                                      NULL,
+                                      self);
+    }
+}
+
+static void
+gbp_spell_dict_set_dict (GbpSpellDict         *self,
+                         const GspellLanguage *language)
+{
+  const gchar *lang_code;
+
+  g_assert (GBP_IS_SPELL_DICT (self));
+
+  if (language != NULL)
+    {
+      lang_code = gspell_language_get_code (language);
+      self->dict = enchant_broker_request_dict (self->broker, lang_code);
+
+      if (self->init_status == INIT_PROCESSING)
+        self->update_needed = TRUE;
+      else
+        {
+          self->init_status = INIT_PROCESSING;
+          gbp_spell_dict_get_words_async (self,
+                                                 gbp_spell_dict_get_dict_words_cb,
+                                                 NULL,
+                                                 self);
+        }
+    }
+  else if (self->dict != NULL)
+    {
+      enchant_broker_free_dict (self->broker, self->dict);
+      self->dict = NULL;
+
+      g_clear_pointer (&self->words, g_hash_table_unref);
+    }
+}
+
+static void
+language_notify_cb (GbpSpellDict  *self,
+                    GParamSpec    *pspec,
+                    GspellChecker *checker)
+{
+  const GspellLanguage *language;
+
+  g_assert (GBP_IS_SPELL_DICT (self));
+  g_assert (GSPELL_IS_CHECKER (checker));
+
+  language = gspell_checker_get_language (self->checker);
+
+  if ((self->language == NULL && language != NULL) ||
+      (self->language != NULL && language == NULL) ||
+      0 != gspell_language_compare (language, self->language))
+    {
+      self->language = language;
+      gbp_spell_dict_set_dict (self, language);
+    }
+}
+
+static void
+checker_weak_ref_cb (gpointer  data,
+                     GObject  *where_the_object_was)
+{
+  GbpSpellDict *self = (GbpSpellDict *)data;
+
+  g_assert (GBP_IS_SPELL_DICT (self));
+
+  self->checker = NULL;
+  self->language = NULL;
+  gbp_spell_dict_set_dict (self, NULL);
+}
+
+GspellChecker *
+gbp_spell_dict_get_checker (GbpSpellDict *self)
+{
+  g_return_val_if_fail (GBP_IS_SPELL_DICT (self), NULL);
+
+  return self->checker;
+}
+
+void
+gbp_spell_dict_set_checker (GbpSpellDict  *self,
+                            GspellChecker *checker)
+{
+  g_return_if_fail (GBP_IS_SPELL_DICT (self));
+
+  if (self->checker != checker)
+    {
+      if (self->checker != NULL)
+        g_object_weak_unref (G_OBJECT (self->checker), checker_weak_ref_cb, self);
+
+      if (checker == NULL)
+        {
+          checker_weak_ref_cb (self, NULL);
+          return;
+        }
+
+      self->checker = checker;
+      g_object_weak_ref (G_OBJECT (self->checker), checker_weak_ref_cb, self);
+      g_signal_connect_object (self->checker,
+                               "notify::language",
+                               G_CALLBACK (language_notify_cb),
+                               self,
+                               G_CONNECT_SWAPPED);
+
+      language_notify_cb (self, NULL, self->checker);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CHECKER]);
+    }
+}
+
+GbpSpellDict *
+gbp_spell_dict_new (GspellChecker *checker)
+{
+  return g_object_new (GBP_TYPE_SPELL_DICT,
+                       "checker", checker,
+                       NULL);
+}
+
+static void
+gbp_spell_dict_finalize (GObject *object)
+{
+  GbpSpellDict *self = (GbpSpellDict *)object;
+
+  if (self->broker != NULL)
+    {
+      if (self->dict != NULL)
+        enchant_broker_free_dict (self->broker, self->dict);
+      g_clear_pointer (&self->broker, enchant_broker_free);
+    }
+
+  if (self->words != NULL)
+    {
+      g_hash_table_remove_all (self->words);
+      g_clear_pointer (&self->words, g_hash_table_unref);
+    }
+
+  G_OBJECT_CLASS (gbp_spell_dict_parent_class)->finalize (object);
+}
+
+static void
+gbp_spell_dict_get_property (GObject    *object,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+  GbpSpellDict *self = GBP_SPELL_DICT (object);
+
+  switch (prop_id)
+    {
+    case PROP_CHECKER:
+      g_value_set_object (value, gbp_spell_dict_get_checker (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_spell_dict_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+  GbpSpellDict *self = GBP_SPELL_DICT (object);
+
+  switch (prop_id)
+    {
+    case PROP_CHECKER:
+      gbp_spell_dict_set_checker (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_spell_dict_loaded (GbpSpellDict *self)
+{
+  g_assert (GBP_IS_SPELL_DICT (self));
+}
+
+static void
+gbp_spell_dict_class_init (GbpSpellDictClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gbp_spell_dict_finalize;
+  object_class->get_property = gbp_spell_dict_get_property;
+  object_class->set_property = gbp_spell_dict_set_property;
+
+  properties [PROP_CHECKER] =
+   g_param_spec_object ("checker",
+                        "Checker",
+                        "Checker",
+                        GSPELL_TYPE_CHECKER,
+                        G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  /**
+   * GbpSpellDict::loaded:
+   *
+   * This signal is emitted when the dictionary is fully initialised.
+   * (for now, personal dictionary loaded)
+   */
+  signals [LOADED] =
+    g_signal_new_class_handler ("loaded",
+                                G_TYPE_FROM_CLASS (klass),
+                                G_SIGNAL_RUN_LAST,
+                                G_CALLBACK (gbp_spell_dict_loaded),
+                                NULL, NULL, NULL,
+                                G_TYPE_NONE,
+                                0);
+}
+
+static void
+gbp_spell_dict_init (GbpSpellDict *self)
+{
+  self->broker = enchant_broker_init ();
+}
diff --git a/plugins/spellcheck/gbp-spell-dict.h b/plugins/spellcheck/gbp-spell-dict.h
new file mode 100644
index 0000000..6eaa132
--- /dev/null
+++ b/plugins/spellcheck/gbp-spell-dict.h
@@ -0,0 +1,42 @@
+/* gbp-spell-dict.h
+ *
+ * Copyright (C) 2016 Sébastien Lafargue <slafargue gnome org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <gspell/gspell.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_SPELL_DICT (gbp_spell_dict_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpSpellDict, gbp_spell_dict, GBP, SPELL_DICT, GObject)
+
+GbpSpellDict  *gbp_spell_dict_new                       (GspellChecker *checker);
+GspellChecker *gbp_spell_dict_get_checker               (GbpSpellDict  *self);
+GPtrArray     *gbp_spell_dict_get_words                 (GbpSpellDict  *self);
+void           gbp_spell_dict_set_checker               (GbpSpellDict  *self,
+                                                         GspellChecker *checker);
+gboolean       gbp_spell_dict_add_word_to_personal      (GbpSpellDict  *self,
+                                                         const gchar   *word);
+gboolean       gbp_spell_dict_remove_word_from_personal (GbpSpellDict  *self,
+                                                         const gchar   *word);
+gboolean       gbp_spell_dict_personal_contains         (GbpSpellDict  *self,
+                                                         const gchar   *word);
+
+G_END_DECLS
diff --git a/plugins/spellcheck/meson.build b/plugins/spellcheck/meson.build
index 91ce6e0..91fcb0a 100644
--- a/plugins/spellcheck/meson.build
+++ b/plugins/spellcheck/meson.build
@@ -10,6 +10,8 @@ spellcheck_sources = [
   'spellcheck-plugin.c',
   'gbp-spell-buffer-addin.c',
   'gbp-spell-buffer-addin.h',
+  'gbp-spell-dict.c',
+  'gbp-spell-dict.h',
   'gbp-spell-editor-addin.c',
   'gbp-spell-editor-addin.h',
   'gbp-spell-editor-view-addin.c',


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