[gnome-builder/wip/slaf/spellcheck-sidebar: 3/33] spellchecker: add ide_editor_spell_navigator
- From: Sébastien Lafargue <slafargue src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/slaf/spellcheck-sidebar: 3/33] spellchecker: add ide_editor_spell_navigator
- Date: Wed, 11 Jan 2017 20:47:05 +0000 (UTC)
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]