[gnome-builder/wip/slaf/spellcheck-sidebar: 3/33] spellchecker: add ide_editor_spell_navigator



commit 77a0da2f8e7e89bc1e6ad14306ee02ae8571ab0b
Author: Sebastien Lafargue <slafargue gnome org>
Date:   Wed Dec 14 11:38:01 2016 +0100

    spellchecker: add ide_editor_spell_navigator
    
    We need a custon navigator for:
    - custom highlight of misspelled words in the view
    - avoid problems between view selection and
      spellchecker UI focus.
    
    - add new features:
       for now, a count of the misspelled word

 libide/Makefile.am                         |    4 +
 libide/editor/ide-editor-spell-navigator.c |  517 ++++++++++++++++++++++++++++
 libide/editor/ide-editor-spell-navigator.h |   38 ++
 libide/editor/ide-editor-spell-utils.c     |  196 +++++++++++
 libide/editor/ide-editor-spell-utils.h     |   38 ++
 libide/editor/ide-editor-spell-widget.c    |   22 +-
 6 files changed, 812 insertions(+), 3 deletions(-)
---
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 1799029..1469589 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -388,6 +388,10 @@ libide_1_0_la_SOURCES =                                   \
        editor/ide-editor-layout-stack-controls.h         \
        editor/ide-editor-print-operation.c               \
        editor/ide-editor-print-operation.h               \
+       editor/ide-editor-spell-navigator.c               \
+       editor/ide-editor-spell-navigator.h               \
+       editor/ide-editor-spell-utils.c                   \
+       editor/ide-editor-spell-utils.h                   \
        editor/ide-editor-spell-widget.c                  \
        editor/ide-editor-spell-widget.h                  \
        editor/ide-editor-tweak-widget.c                  \
diff --git a/libide/editor/ide-editor-spell-navigator.c b/libide/editor/ide-editor-spell-navigator.c
new file mode 100644
index 0000000..916d953
--- /dev/null
+++ b/libide/editor/ide-editor-spell-navigator.c
@@ -0,0 +1,517 @@
+/* ide-editor-spell-navigator.c
+ *
+ * Copyright (C) 2016 Sebastien 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 code is a modification of:
+ * https://git.gnome.org/browse/gspell/tree/gspell/gspell-navigator-text-view.c
+ */
+
+#include <glib/gi18n.h>
+#include "sourceview/ide-source-view.h"
+
+#include "ide-editor-spell-navigator.h"
+#include "ide-editor-spell-utils.h"
+
+struct _IdeEditorSpellNavigator
+{
+  GObject        parent_instance;
+
+  GtkTextView   *view;
+  GtkTextBuffer *buffer;
+  GHashTable    *words_count;
+  GtkTextMark   *start_boundary;
+  GtkTextMark   *end_boundary;
+  GtkTextMark   *word_start;
+  GtkTextMark   *word_end;
+};
+
+static void gspell_navigator_iface_init (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_EXTENDED (IdeEditorSpellNavigator, ide_editor_spell_navigator, G_TYPE_INITIALLY_UNOWNED, 0,
+                        G_IMPLEMENT_INTERFACE (GSPELL_TYPE_NAVIGATOR, gspell_navigator_iface_init))
+
+enum {
+  PROP_0,
+  PROP_VIEW,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+/* TODO: do it async */
+
+/* Always process start and end by init_boudaries before */
+static GHashTable *
+ide_editor_spell_navigator_count_words (IdeEditorSpellNavigator *self,
+                                        GtkTextIter             *start,
+                                        GtkTextIter             *end)
+{
+  GHashTable *table;
+  GtkTextTag *no_spell_check_tag;
+  GtkTextIter word_start;
+  GtkTextIter word_end;
+  guint count;
+  gchar *word;
+
+  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);
+
+  table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+  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 (table, word))))
+        count++;
+      else
+        count = 1;
+
+      g_hash_table_insert (table, word, GUINT_TO_POINTER (count));
+
+      word_start = word_end;
+    }
+
+  return table;
+}
+
+guint
+ide_editor_spell_navigator_get_count (IdeEditorSpellNavigator *self,
+                                      const gchar             *word)
+{
+  g_assert (IDE_IS_EDITOR_SPELL_NAVIGATOR (self));
+
+  if (ide_str_empty0 (word))
+    return 0;
+  else
+    return GPOINTER_TO_UINT (g_hash_table_lookup (self->words_count, word));
+}
+
+GspellNavigator *
+ide_editor_spell_navigator_new (GtkTextView *view)
+{
+  return g_object_new (IDE_TYPE_EDITOR_SPELL_NAVIGATOR,
+                       "view", view,
+                       NULL);
+}
+
+static void
+delete_mark (GtkTextBuffer *buffer,
+             GtkTextMark   *mark)
+{
+  if (mark != NULL)
+    gtk_text_buffer_delete_mark (buffer, mark);
+}
+
+static void
+ide_editor_spell_navigator_dispose (GObject *object)
+{
+  IdeEditorSpellNavigator *self = (IdeEditorSpellNavigator *)object;
+
+  g_clear_object (&self->view);
+  g_hash_table_unref (self->words_count);
+
+  if (self->buffer != NULL)
+    {
+      delete_mark (self->buffer, self->start_boundary);
+      delete_mark (self->buffer, self->end_boundary);
+      delete_mark (self->buffer, self->word_start);
+      delete_mark (self->buffer, self->word_end);
+
+      g_object_unref (self->buffer);
+      self->buffer = NULL;
+    }
+
+  G_OBJECT_CLASS (ide_editor_spell_navigator_parent_class)->dispose (object);
+}
+
+/* static void */
+/* ide_editor_spell_navigator_finalize (GObject *object) */
+/* { */
+/*   IdeEditorSpellNavigator *self = (IdeEditorSpellNavigator *)object; */
+
+/*   G_OBJECT_CLASS (ide_editor_spell_navigator_parent_class)->finalize (object); */
+/* } */
+
+static void
+init_boundaries (IdeEditorSpellNavigator *self)
+{
+  GtkTextIter start;
+  GtkTextIter end;
+
+  g_assert (IDE_IS_EDITOR_SPELL_NAVIGATOR (self));
+  g_assert (self->start_boundary == NULL);
+  g_assert (self->end_boundary == NULL);
+
+  if (!gtk_text_buffer_get_selection_bounds (self->buffer, &start, &end))
+    gtk_text_buffer_get_bounds (self->buffer, &start, &end);
+
+  if (ide_editor_spell_utils_text_iter_inside_word (&start) &&
+      !ide_editor_spell_utils_text_iter_starts_word (&start))
+    ide_editor_spell_utils_text_iter_backward_word_start (&start);
+
+  if (ide_editor_spell_utils_text_iter_inside_word (&end))
+    ide_editor_spell_utils_text_iter_forward_word_end (&end);
+
+  self->start_boundary = gtk_text_buffer_create_mark (self->buffer, NULL, &start, TRUE);
+  self->end_boundary = gtk_text_buffer_create_mark (self->buffer, NULL, &end, FALSE);
+}
+
+static void
+set_view (IdeEditorSpellNavigator *self,
+          GtkTextView             *view)
+{
+  GtkTextIter start;
+  GtkTextIter end;
+
+  g_assert (IDE_IS_EDITOR_SPELL_NAVIGATOR (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));
+
+  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);
+
+  g_object_notify (G_OBJECT (self), "view");
+}
+
+static void
+ide_editor_spell_navigator_set_property (GObject      *object,
+                                         guint         prop_id,
+                                         const GValue *value,
+                                         GParamSpec   *pspec)
+{
+  IdeEditorSpellNavigator *self = IDE_EDITOR_SPELL_NAVIGATOR (object);
+
+  switch (prop_id)
+    {
+    case PROP_VIEW:
+      set_view (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_editor_spell_navigator_get_property (GObject    *object,
+                                         guint       prop_id,
+                                         GValue     *value,
+                                         GParamSpec *pspec)
+{
+  IdeEditorSpellNavigator *self = IDE_EDITOR_SPELL_NAVIGATOR (object);
+
+  switch (prop_id)
+    {
+    case PROP_VIEW:
+      g_value_set_object (value, self->view);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_editor_spell_navigator_class_init (IdeEditorSpellNavigatorClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = ide_editor_spell_navigator_dispose;
+  object_class->get_property = ide_editor_spell_navigator_get_property;
+  object_class->set_property = ide_editor_spell_navigator_set_property;
+
+  properties [PROP_VIEW] =
+    g_param_spec_object ("view",
+                        "View",
+                        "the view",
+                        GTK_TYPE_TEXT_VIEW,
+                        G_PARAM_READWRITE |
+                        G_PARAM_CONSTRUCT_ONLY |
+                        G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_editor_spell_navigator_init (IdeEditorSpellNavigator *self)
+{
+}
+
+static void
+select_misspelled_word (IdeEditorSpellNavigator *self)
+{
+  GtkTextIter word_start;
+  GtkTextIter word_end;
+
+  g_assert (IDE_IS_EDITOR_SPELL_NAVIGATOR (self));
+
+  gtk_text_buffer_get_iter_at_mark (self->buffer, &word_start, self->word_start);
+  gtk_text_buffer_get_iter_at_mark (self->buffer, &word_end, self->word_end);
+
+  gtk_text_buffer_select_range (self->buffer, &word_start, &word_end);
+
+  g_return_if_fail (gtk_text_view_get_buffer (self->view) == self->buffer);
+
+  gtk_text_view_scroll_to_mark (self->view,
+                                gtk_text_buffer_get_insert (self->buffer),
+                                0.25,
+                                FALSE,
+                                0.0,
+                                0.0);
+}
+
+static gboolean
+ide_editor_spell_navigator_goto_next (GspellNavigator  *navigator,
+                                      gchar           **word_p,
+                                      GspellChecker   **spell_checker_p,
+                                      GError          **error_p)
+{
+  IdeEditorSpellNavigator *self = (IdeEditorSpellNavigator *)navigator;
+  GspellTextBuffer *gspell_buffer;
+  GspellChecker *spell_checker;
+  GtkTextIter word_start;
+  GtkTextIter end;
+  GtkTextTag *no_spell_check_tag;
+
+  g_assert (IDE_IS_EDITOR_SPELL_NAVIGATOR (self));
+  g_assert ((self->word_start == NULL && self->word_end == NULL) ||
+            (self->word_start != NULL && self->word_end != NULL));
+
+  gspell_buffer = gspell_text_buffer_get_from_gtk_text_buffer (self->buffer);
+  spell_checker = gspell_text_buffer_get_spell_checker (gspell_buffer);
+
+  if (spell_checker == NULL)
+    return FALSE;
+
+  if (gspell_checker_get_language (spell_checker) == NULL)
+    {
+      if (spell_checker_p != NULL)
+        *spell_checker_p = g_object_ref (spell_checker);
+
+      g_set_error (error_p,
+                   GSPELL_CHECKER_ERROR,
+                   GSPELL_CHECKER_ERROR_NO_LANGUAGE_SET,
+                   "%s",
+                   _("Spell checker error: no language set. "
+                   "It’s maybe because no dictionaries are installed."));
+
+      return FALSE;
+    }
+
+  gtk_text_buffer_get_iter_at_mark (self->buffer, &end, self->end_boundary);
+
+  if (self->word_start == NULL)
+    {
+      GtkTextIter start;
+
+      gtk_text_buffer_get_iter_at_mark (self->buffer, &start, self->start_boundary);
+
+      self->word_start = gtk_text_buffer_create_mark (self->buffer, NULL, &start, TRUE);
+      self->word_end = gtk_text_buffer_create_mark (self->buffer, NULL, &start, FALSE);
+
+      word_start = start;
+    }
+  else
+    {
+      GtkTextIter word_end;
+
+      gtk_text_buffer_get_iter_at_mark (self->buffer, &word_end, self->word_end);
+
+      if (gtk_text_iter_compare (&end, &word_end) <= 0)
+        return FALSE;
+
+      word_start = word_end;
+    }
+
+  no_spell_check_tag = ide_editor_spell_utils_get_no_spell_check_tag (self->buffer);
+
+  while (TRUE)
+    {
+      GtkTextIter word_end;
+      g_autofree gchar *word = NULL;
+      gboolean correctly_spelled;
+      GError *error = NULL;
+
+      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))
+            return FALSE;
+
+          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))
+          return FALSE;
+
+        g_return_val_if_fail (ide_editor_spell_utils_text_iter_starts_word (&word_start), FALSE);
+
+        word_end = word_start;
+        ide_editor_spell_utils_text_iter_forward_word_end (&word_end);
+
+        if (gtk_text_iter_compare (&end, &word_end) < 0)
+          return FALSE;
+
+        word = gtk_text_buffer_get_text (self->buffer, &word_start, &word_end, FALSE);
+        correctly_spelled = gspell_checker_check_word (spell_checker, word, -1, &error);
+
+        if (error != NULL)
+          {
+            g_propagate_error (error_p, error);
+            return FALSE;
+          }
+
+        if (!correctly_spelled)
+          {
+            /* Found! */
+            gtk_text_buffer_move_mark (self->buffer, self->word_start, &word_start);
+            gtk_text_buffer_move_mark (self->buffer, self->word_end, &word_end);
+            select_misspelled_word (self);
+
+            if (spell_checker_p != NULL)
+              *spell_checker_p = g_object_ref (spell_checker);
+
+            if (word_p != NULL)
+              *word_p = g_steal_pointer (&word);
+
+            return TRUE;
+         }
+
+        word_start = word_end;
+    }
+
+  return FALSE;
+}
+
+static void
+ide_editor_spell_navigator_change (GspellNavigator *navigator,
+                                   const gchar     *word,
+                                   const gchar     *change_to)
+{
+  IdeEditorSpellNavigator *self = (IdeEditorSpellNavigator *)navigator;
+  GtkTextIter word_start;
+  GtkTextIter word_end;
+  g_autofree gchar *word_in_buffer = NULL;
+
+  g_assert (IDE_IS_EDITOR_SPELL_NAVIGATOR (self));
+  g_assert (GTK_IS_TEXT_MARK (self->word_start));
+  g_assert (GTK_IS_TEXT_MARK (self->word_end));
+
+  gtk_text_buffer_get_iter_at_mark (self->buffer, &word_start, self->word_start);
+  gtk_text_buffer_get_iter_at_mark (self->buffer, &word_end, self->word_end);
+
+  word_in_buffer = gtk_text_buffer_get_slice (self->buffer, &word_start, &word_end, TRUE);
+  g_return_if_fail (word_in_buffer != NULL);
+  g_return_if_fail (g_strcmp0 (word_in_buffer, word) == 0);
+
+  gtk_text_buffer_begin_user_action (self->buffer);
+
+  gtk_text_buffer_delete (self->buffer, &word_start, &word_end);
+  gtk_text_buffer_insert (self->buffer, &word_start, change_to, -1);
+
+  gtk_text_buffer_end_user_action (self->buffer);
+}
+
+static void
+ide_editor_spell_navigator_change_all (GspellNavigator *navigator,
+                                       const gchar     *word,
+                                       const gchar     *change_to)
+{
+  IdeEditorSpellNavigator *self = (IdeEditorSpellNavigator *)navigator;
+  GtkTextIter iter;
+
+  g_assert (IDE_IS_EDITOR_SPELL_NAVIGATOR (self));
+  g_assert (GTK_IS_TEXT_MARK (self->start_boundary));
+  g_assert (GTK_IS_TEXT_MARK (self->end_boundary));
+
+  gtk_text_buffer_get_iter_at_mark (self->buffer, &iter, self->start_boundary);
+  gtk_text_buffer_begin_user_action (self->buffer);
+
+  while (TRUE)
+  {
+    gboolean found;
+    GtkTextIter match_start;
+    GtkTextIter match_end;
+    GtkTextIter limit;
+
+    gtk_text_buffer_get_iter_at_mark (self->buffer, &limit, self->end_boundary);
+    found = gtk_text_iter_forward_search (&iter,
+                                          word,
+                                          GTK_TEXT_SEARCH_VISIBLE_ONLY |
+                                          GTK_TEXT_SEARCH_TEXT_ONLY,
+                                          &match_start,
+                                          &match_end,
+                                          &limit);
+
+    if (!found)
+      break;
+
+    if (ide_editor_spell_utils_text_iter_starts_word (&match_start) &&
+        ide_editor_spell_utils_text_iter_ends_word (&match_end))
+      {
+        gtk_text_buffer_delete (self->buffer, &match_start, &match_end);
+        gtk_text_buffer_insert (self->buffer, &match_end, change_to, -1);
+      }
+
+    iter = match_end;
+  }
+
+  gtk_text_buffer_end_user_action (self->buffer);
+}
+
+static void
+gspell_navigator_iface_init (gpointer g_iface,
+                             gpointer iface_data)
+{
+  GspellNavigatorInterface *iface = g_iface;
+
+  iface->goto_next = ide_editor_spell_navigator_goto_next;
+  iface->change = ide_editor_spell_navigator_change;
+  iface->change_all = ide_editor_spell_navigator_change_all;
+}
diff --git a/libide/editor/ide-editor-spell-navigator.h b/libide/editor/ide-editor-spell-navigator.h
new file mode 100644
index 0000000..34c7513
--- /dev/null
+++ b/libide/editor/ide-editor-spell-navigator.h
@@ -0,0 +1,38 @@
+/* ide-editor-spell-navigator.h
+ *
+ * Copyright (C) 2016 Sebastien 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/>.
+ */
+
+#ifndef IDE_EDITOR_SPELL_NAVIGATOR_H
+#define IDE_EDITOR_SPELL_NAVIGATOR_H
+
+#include <glib-object.h>
+#include <gspell/gspell.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_EDITOR_SPELL_NAVIGATOR (ide_editor_spell_navigator_get_type())
+
+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);
+
+G_END_DECLS
+
+#endif /* IDE_EDITOR_SPELL_NAVIGATOR_H */
+
diff --git a/libide/editor/ide-editor-spell-utils.c b/libide/editor/ide-editor-spell-utils.c
new file mode 100644
index 0000000..e246b68
--- /dev/null
+++ b/libide/editor/ide-editor-spell-utils.c
@@ -0,0 +1,196 @@
+/* ide-editor-spell-utils.c
+ *
+ * Copyright (C) 2016 Sebastien 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 code is mostly from:
+ * https://git.gnome.org/browse/gspell/tree/gspell/gspell-utils.c
+ * https://git.gnome.org/browse/gspell/tree/gspell/gspell-text-iter.c
+ */
+
+#include <gtk/gtk.h>
+
+#include "ide-editor-spell-utils.h"
+
+/* gunichar decimal value of unicode apostrophe characters. */
+#define GSPELL_MODIFIER_LETTER_APOSTROPHE (700) /* U+02BC */
+#define GSPELL_RIGHT_SINGLE_QUOTATION_MARK (8217) /* U+2019 */
+
+static inline gboolean
+is__text_iter_apostrophe_or_dash (const GtkTextIter *iter)
+{
+  gunichar ch;
+
+  ch = gtk_text_iter_get_char (iter);
+
+  return (ch == '-' ||
+          ch == '\'' ||
+          ch == GSPELL_MODIFIER_LETTER_APOSTROPHE ||
+          ch == GSPELL_RIGHT_SINGLE_QUOTATION_MARK);
+}
+
+gboolean
+ide_editor_spell_utils_text_iter_forward_word_end (GtkTextIter *iter)
+{
+  g_return_val_if_fail (iter != NULL, FALSE);
+
+  while (gtk_text_iter_forward_word_end (iter))
+    {
+      GtkTextIter next_char;
+
+      if (!is__text_iter_apostrophe_or_dash (iter))
+        return TRUE;
+
+      next_char = *iter;
+      gtk_text_iter_forward_char (&next_char);
+      if (!gtk_text_iter_starts_word (&next_char))
+        return TRUE;
+
+      *iter = next_char;
+    }
+
+  return FALSE;
+}
+
+gboolean
+ide_editor_spell_utils_text_iter_backward_word_start (GtkTextIter *iter)
+{
+  g_return_val_if_fail (iter != NULL, FALSE);
+
+  while (gtk_text_iter_backward_word_start (iter))
+    {
+      GtkTextIter prev_char = *iter;
+
+      if (!gtk_text_iter_backward_char (&prev_char) ||
+          !is__text_iter_apostrophe_or_dash (&prev_char) ||
+          !gtk_text_iter_ends_word (&prev_char))
+        return TRUE;
+
+      *iter = prev_char;
+    }
+
+  return FALSE;
+}
+
+gboolean
+ide_editor_spell_utils_text_iter_starts_word (const GtkTextIter *iter)
+{
+  GtkTextIter prev_char;
+
+  g_return_val_if_fail (iter != NULL, FALSE);
+
+  if (!gtk_text_iter_starts_word (iter))
+    return FALSE;
+
+  prev_char = *iter;
+  if (!gtk_text_iter_backward_char (&prev_char))
+    return TRUE;
+
+  if (is__text_iter_apostrophe_or_dash (&prev_char) &&
+      gtk_text_iter_ends_word (&prev_char))
+    return FALSE;
+
+  return TRUE;
+}
+
+gboolean
+ide_editor_spell_utils_text_iter_ends_word (const GtkTextIter *iter)
+{
+  GtkTextIter next_char;
+
+  g_return_val_if_fail (iter != NULL, FALSE);
+
+  if (!gtk_text_iter_ends_word (iter))
+    return FALSE;
+
+  if (gtk_text_iter_is_end (iter))
+    return TRUE;
+
+  next_char = *iter;
+  gtk_text_iter_forward_char (&next_char);
+
+  if (is__text_iter_apostrophe_or_dash (iter) &&
+      gtk_text_iter_starts_word (&next_char))
+    return FALSE;
+
+  return TRUE;
+}
+
+gboolean
+ide_editor_spell_utils_text_iter_inside_word (const GtkTextIter *iter)
+{
+  g_return_val_if_fail (iter != NULL, FALSE);
+
+  if (gtk_text_iter_inside_word (iter))
+    return TRUE;
+
+  if (gtk_text_iter_ends_word (iter) &&
+      is__text_iter_apostrophe_or_dash (iter))
+    {
+      GtkTextIter next_char = *iter;
+      gtk_text_iter_forward_char (&next_char);
+      return gtk_text_iter_starts_word (&next_char);
+    }
+
+  return FALSE;
+}
+
+GtkTextTag *
+ide_editor_spell_utils_get_no_spell_check_tag (GtkTextBuffer *buffer)
+{
+  GtkTextTagTable *tag_table;
+
+  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
+
+  tag_table = gtk_text_buffer_get_tag_table (buffer);
+
+  return gtk_text_tag_table_lookup (tag_table, "gtksourceview:context-classes:no-spell-check");
+}
+
+gboolean
+ide_editor_spell_utils_skip_no_spell_check (GtkTextTag        *no_spell_check_tag,
+                                            GtkTextIter       *start,
+                                            const GtkTextIter *end)
+{
+  g_return_val_if_fail (start != NULL, FALSE);
+  g_return_val_if_fail (end != NULL, FALSE);
+
+  if (no_spell_check_tag == NULL)
+    return TRUE;
+
+  g_return_val_if_fail (GTK_IS_TEXT_TAG (no_spell_check_tag), FALSE);
+
+  while (gtk_text_iter_has_tag (start, no_spell_check_tag))
+    {
+      GtkTextIter last = *start;
+
+      if (!gtk_text_iter_forward_to_tag_toggle (start, no_spell_check_tag))
+        return FALSE;
+
+      if (gtk_text_iter_compare (start, &last) <= 0)
+        return FALSE;
+
+      ide_editor_spell_utils_text_iter_forward_word_end (start);
+      ide_editor_spell_utils_text_iter_backward_word_start (start);
+
+      if (gtk_text_iter_compare (start, &last) <= 0)
+        return FALSE;
+
+      if (gtk_text_iter_compare (start, end) >= 0)
+        return FALSE;
+  }
+
+  return TRUE;
+}
diff --git a/libide/editor/ide-editor-spell-utils.h b/libide/editor/ide-editor-spell-utils.h
new file mode 100644
index 0000000..1f36f9b
--- /dev/null
+++ b/libide/editor/ide-editor-spell-utils.h
@@ -0,0 +1,38 @@
+/* ide-editor-spell-utils.h
+ *
+ * Copyright (C) 2016 Sebastien 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/>.
+ */
+
+#ifndef IDE_EDITOR_SPELL_UTILS_H
+#define IDE_EDITOR_SPELL_UTILS_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+gboolean      ide_editor_spell_utils_text_iter_forward_word_end        (GtkTextIter        *iter);
+gboolean      ide_editor_spell_utils_text_iter_backward_word_start     (GtkTextIter        *iter);
+gboolean      ide_editor_spell_utils_text_iter_starts_word             (const GtkTextIter  *iter);
+gboolean      ide_editor_spell_utils_text_iter_ends_word               (const GtkTextIter  *iter);
+gboolean      ide_editor_spell_utils_text_iter_inside_word             (const GtkTextIter  *iter);
+GtkTextTag   *ide_editor_spell_utils_get_no_spell_check_tag            (GtkTextBuffer      *buffer);
+gboolean      ide_editor_spell_utils_skip_no_spell_check               (GtkTextTag         
*no_spell_check_tag,
+                                                                        GtkTextIter        *start,
+                                                                        const GtkTextIter  *end);
+
+G_END_DECLS
+
+#endif /* IDE_EDITOR_SPELL_UTILS_H */
diff --git a/libide/editor/ide-editor-spell-widget.c b/libide/editor/ide-editor-spell-widget.c
index 2f02f39..d4b5cad 100644
--- a/libide/editor/ide-editor-spell-widget.c
+++ b/libide/editor/ide-editor-spell-widget.c
@@ -20,6 +20,8 @@
 #include <glib/gi18n.h>
 #include <gspell/gspell.h>
 
+#include "ide-editor-spell-navigator.h"
+
 #include "ide-editor-spell-widget.h"
 
 struct _IdeEditorSpellWidget
@@ -33,6 +35,7 @@ struct _IdeEditorSpellWidget
   const GspellLanguage  *spellchecker_language;
 
   GtkLabel              *word_label;
+  GtkLabel              *count_label;
   GtkEntry              *word_entry;
   GtkButton             *check_button;
   GtkButton             *add_dict_button;
@@ -158,6 +161,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;
 
@@ -166,6 +170,18 @@ 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);
       fill_suggestions_box (self, word, &first_result);
       if (!ide_str_empty0 (first_result))
@@ -215,7 +231,7 @@ ide_editor_spell_widget_set_view (IdeEditorSpellWidget *self,
   if (GSPELL_IS_NAVIGATOR (self->navigator))
     g_clear_object (&self->navigator);
 
-  self->navigator = gspell_navigator_text_view_new (GTK_TEXT_VIEW (view));
+  self->navigator = ide_editor_spell_navigator_new (GTK_TEXT_VIEW (view));
 }
 
 static void
@@ -483,7 +499,7 @@ ide_editor_spell_widget_constructed (GObject *object)
   gspell_language_chooser_set_language (GSPELL_LANGUAGE_CHOOSER (self->language_chooser_button),
                                         self->spellchecker_language);
 
-  self->navigator = gspell_navigator_text_view_new (GTK_TEXT_VIEW (self->view));
+  self->navigator = ide_editor_spell_navigator_new (GTK_TEXT_VIEW (self->view));
 
   g_signal_connect_swapped (self->word_entry,
                             "changed",
@@ -569,7 +585,6 @@ ide_editor_spell_widget_finalize (GObject *object)
   const GspellLanguage *spell_language;
   GtkTextBuffer *buffer;
 
-  printf ("ide_editor_spell_widget_finalize\n");
   g_clear_object (&self->navigator);
 
   /* Set back the view spellchecking previous state */
@@ -653,6 +668,7 @@ ide_editor_spell_widget_class_init (IdeEditorSpellWidgetClass *klass)
 
   gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/ui/ide-editor-spell-widget.ui");
   gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, word_label);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, count_label);
   gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, word_entry);
   gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, check_button);
   gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, add_dict_button);


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