[gnome-text-editor/wip/chergert/spelling-cursor] spellcheck: use sub-iterators to seek further
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-text-editor/wip/chergert/spelling-cursor] spellcheck: use sub-iterators to seek further
- Date: Sat, 10 Jul 2021 23:12:26 +0000 (UTC)
commit 57e4a7af2e7118a242ef30b7ebedd77271b49e2f
Author: Christian Hergert <chergert redhat com>
Date: Sat Jul 10 15:53:18 2021 -0700
spellcheck: use sub-iterators to seek further
The goal here is to avoid looking at content from the buffer if we know
that it is either in a region that has 'no-spell-check' context or part
of the TextRegion that we know is already checked.
This tries to employ something similar to how you would do an n-way merge
(very hand-wavy) by applying the forward progress from some sub-iterators
to the others.
There are still some invalidation bugs we need to take care of, but this is
a start in that direction.
src/editor-spell-cursor.c | 283 ++++++++++++++++++++++++++-------
src/editor-spell-cursor.h | 31 ++--
src/editor-text-buffer-spell-adapter.c | 133 +++++++++-------
src/meson.build | 6 +
src/test-spell-cursor.c | 59 +++++++
5 files changed, 375 insertions(+), 137 deletions(-)
---
diff --git a/src/editor-spell-cursor.c b/src/editor-spell-cursor.c
index 0800c87..d641c36 100644
--- a/src/editor-spell-cursor.c
+++ b/src/editor-spell-cursor.c
@@ -20,46 +20,132 @@
#include "config.h"
+#include "cjhtextregionprivate.h"
#include "editor-spell-cursor.h"
-static char *
-editor_spell_cursor_word (EditorSpellCursor *cursor)
+#define RUN_UNCHECKED NULL
+
+typedef struct
+{
+ CjhTextRegion *region;
+ GtkTextBuffer *buffer;
+ gssize pos;
+} RegionIter;
+
+typedef struct
{
- g_assert (cursor != NULL);
- g_assert (!cursor->exhausted);
+ GtkTextBuffer *buffer;
+ GtkTextTag *tag;
+ GtkTextIter pos;
+} TagIter;
- return gtk_text_iter_get_slice (&cursor->word_begin, &cursor->word_end);
+typedef struct
+{
+ GtkTextBuffer *buffer;
+ GtkTextIter word_begin;
+ GtkTextIter word_end;
+} WordIter;
+
+struct _EditorSpellCursor
+{
+ RegionIter region;
+ TagIter tag;
+ WordIter word;
+};
+
+static void
+region_iter_init (RegionIter *self,
+ GtkTextBuffer *buffer,
+ CjhTextRegion *region)
+{
+ self->region = region;
+ self->buffer = buffer;
+ self->pos = -1;
}
-void
-editor_spell_cursor_init (EditorSpellCursor *cursor,
- const GtkTextIter *begin,
- const GtkTextIter *end,
- GtkTextTag *misspelled_tag)
+static gboolean
+region_iter_next_cb (gsize position,
+ const CjhTextRegionRun *run,
+ gpointer user_data)
{
- g_return_if_fail (cursor != NULL);
- g_return_if_fail (begin != NULL);
- g_return_if_fail (end != NULL);
- g_return_if_fail (gtk_text_iter_get_buffer (begin) == gtk_text_iter_get_buffer (end));
+ if (run->data == RUN_UNCHECKED)
+ {
+ gsize *pos = user_data;
+ *pos = position;
+ return TRUE;
+ }
- cursor->buffer = gtk_text_iter_get_buffer (begin);
- cursor->misspelled_tag = misspelled_tag;
- cursor->begin = *begin;
- cursor->end = *end;
- cursor->exhausted = FALSE;
+ return FALSE;
+}
- gtk_text_iter_order (&cursor->begin, &cursor->end);
- gtk_text_iter_backward_word_start (&cursor->begin);
- gtk_text_iter_forward_word_end (&cursor->end);
+static gboolean
+region_iter_next (RegionIter *self,
+ GtkTextIter *iter)
+{
+ gsize pos, new_pos;
+
+ if (self->pos >= (gssize)_cjh_text_region_get_length (self->region))
+ {
+ gtk_text_buffer_get_end_iter (self->buffer, iter);
+ return FALSE;
+ }
+
+ if (self->pos < 0)
+ pos = 0;
+ else
+ pos = self->pos;
+
+ _cjh_text_region_foreach_in_range (self->region,
+ pos,
+ _cjh_text_region_get_length (self->region),
+ region_iter_next_cb,
+ &new_pos);
+
+ pos = MAX (pos, new_pos);
+ gtk_text_buffer_get_iter_at_offset (self->buffer, iter, pos);
+ self->pos = pos;
+
+ return TRUE;
+}
- cursor->word_begin = cursor->begin;
- cursor->word_end = cursor->begin;
+static void
+region_iter_seek (RegionIter *self,
+ const GtkTextIter *iter)
+{
+ /* Move to position past the word */
+ self->pos = gtk_text_iter_get_offset (iter) + 1;
+}
- /* Clear the tag for the (possibly extended) region */
- gtk_text_buffer_remove_tag (cursor->buffer,
- cursor->misspelled_tag,
- &cursor->begin,
- &cursor->end);
+static void
+tag_iter_init (TagIter *self,
+ GtkTextBuffer *buffer,
+ GtkTextTag *tag)
+{
+ self->buffer = buffer;
+ self->tag = tag;
+ gtk_text_buffer_get_start_iter (buffer, &self->pos);
+}
+
+static gboolean
+tag_iter_next (TagIter *self,
+ GtkTextIter *pos)
+{
+ if (self->tag && gtk_text_iter_has_tag (&self->pos, self->tag))
+ {
+ /* Should always succeed because we are within the tag */
+ gtk_text_iter_forward_to_tag_toggle (&self->pos, self->tag);
+ }
+
+ *pos = self->pos;
+
+ return TRUE;
+}
+
+static void
+tag_iter_seek (TagIter *self,
+ const GtkTextIter *iter)
+{
+ self->pos = *iter;
}
static gboolean
@@ -94,56 +180,137 @@ backward_word_start (GtkTextIter *iter)
return FALSE;
}
-char *
-editor_spell_cursor_next_word (EditorSpellCursor *cursor)
+static void
+word_iter_init (WordIter *self,
+ GtkTextBuffer *buffer)
{
- g_return_val_if_fail (cursor != NULL, NULL);
-
- if (cursor->exhausted)
- return NULL;
+ self->buffer = buffer;
+ gtk_text_buffer_get_start_iter (buffer, &self->word_begin);
+ self->word_end = self->word_begin;
+}
- if (!forward_word_end (&cursor->word_end))
- goto exhausted;
+static gboolean
+word_iter_next (WordIter *self,
+ GtkTextIter *word_begin,
+ GtkTextIter *word_end)
+{
+ if (!forward_word_end (&self->word_end))
+ {
+ *word_begin = self->word_end;
+ *word_end = self->word_end;
+ return FALSE;
+ }
+
+ self->word_begin = self->word_end;
+
+ if (!backward_word_start (&self->word_begin))
+ {
+ *word_begin = self->word_end;
+ *word_end = self->word_end;
+ return FALSE;
+ }
+
+ *word_begin = self->word_begin;
+ *word_end = self->word_end;
+
+ return TRUE;
+}
- cursor->word_begin = cursor->word_end;
+static void
+word_iter_seek (WordIter *self,
+ const GtkTextIter *iter)
+{
+ self->word_begin = *iter;
+ self->word_end = *iter;
+}
- if (!backward_word_start (&cursor->word_begin))
- goto exhausted;
+EditorSpellCursor *
+editor_spell_cursor_new (GtkTextBuffer *buffer,
+ CjhTextRegion *region,
+ GtkTextTag *no_spell_check_tag)
+{
+ EditorSpellCursor *self;
- return editor_spell_cursor_word (cursor);
+ g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
+ g_return_val_if_fail (region != NULL, NULL);
+ g_return_val_if_fail (!no_spell_check_tag || GTK_IS_TEXT_TAG (no_spell_check_tag), NULL);
-exhausted:
- cursor->exhausted = TRUE;
+ self = g_rc_box_new0 (EditorSpellCursor);
+ region_iter_init (&self->region, buffer, region);
+ tag_iter_init (&self->tag, buffer, no_spell_check_tag);
+ word_iter_init (&self->word, buffer);
- return NULL;
+ return self;
}
void
-editor_spell_cursor_tag (EditorSpellCursor *cursor)
+editor_spell_cursor_free (EditorSpellCursor *self)
{
- g_return_if_fail (cursor != NULL);
-
- gtk_text_buffer_apply_tag (cursor->buffer,
- cursor->misspelled_tag,
- &cursor->word_begin,
- &cursor->word_end);
+ g_rc_box_release (self);
}
-gboolean
-editor_spell_cursor_contains_tag (EditorSpellCursor *cursor,
- GtkTextTag *tag)
+static gboolean
+contains_tag (const GtkTextIter *word_begin,
+ const GtkTextIter *word_end,
+ GtkTextTag *tag)
{
GtkTextIter toggle_iter;
- if (tag == NULL || cursor->exhausted)
+ if (tag == NULL)
return FALSE;
- if (gtk_text_iter_has_tag (&cursor->word_begin, tag))
+ if (gtk_text_iter_has_tag (word_begin, tag))
return TRUE;
- toggle_iter = cursor->word_begin;
+ toggle_iter = *word_begin;
if (!gtk_text_iter_forward_to_tag_toggle (&toggle_iter, tag))
return FALSE;
- return gtk_text_iter_compare (&cursor->word_end, &toggle_iter) > 0;
+ return gtk_text_iter_compare (word_end, &toggle_iter) > 0;
+}
+
+gboolean
+editor_spell_cursor_next (EditorSpellCursor *self,
+ GtkTextIter *word_begin,
+ GtkTextIter *word_end)
+{
+ /* Try to advance skipping any checked region in the buffer */
+ if (!region_iter_next (&self->region, word_end))
+ {
+ *word_begin = *word_end;
+ return FALSE;
+ }
+
+ /* Pass that position to the next iter, so it can skip
+ * past anything that is already checked. Then try to move
+ * forward so that we can skip past regions in the text
+ * buffer that are to be ignored by spellcheck.
+ */
+ tag_iter_seek (&self->tag, word_end);
+ if (!tag_iter_next (&self->tag, word_end))
+ {
+ *word_begin = *word_end;
+ return FALSE;
+ }
+
+ /* Now pass that information to the word iter, so that it can
+ * jump forward to the next word starting from our tag/region
+ * positions.
+ */
+ word_iter_seek (&self->word, word_end);
+ if (!word_iter_next (&self->word, word_begin, word_end))
+ return FALSE;
+
+ /* Now pass our new position to the region so that it will
+ * skip past the word when advancing.
+ */
+ region_iter_seek (&self->region, word_end);
+
+ /* If this word contains the no-spell-check tag, then try
+ * again to skip past even more content.
+ */
+ if (contains_tag (word_begin, word_end, self->tag.tag))
+ return editor_spell_cursor_next (self, word_begin, word_end);
+
+ return TRUE;
}
diff --git a/src/editor-spell-cursor.h b/src/editor-spell-cursor.h
index 230fc13..f813199 100644
--- a/src/editor-spell-cursor.h
+++ b/src/editor-spell-cursor.h
@@ -1,4 +1,4 @@
-/* editor-spell-cursor.h
+/* editor-spell-cursor.c
*
* Copyright 2021 Christian Hergert <chergert redhat com>
*
@@ -24,24 +24,17 @@
G_BEGIN_DECLS
-typedef struct
-{
- GtkTextBuffer *buffer;
- GtkTextTag *misspelled_tag;
- GtkTextIter begin;
- GtkTextIter end;
- GtkTextIter word_begin;
- GtkTextIter word_end;
- guint exhausted : 1;
-} EditorSpellCursor;
+typedef struct _EditorSpellCursor EditorSpellCursor;
+typedef struct _CjhTextRegion CjhTextRegion;
-void editor_spell_cursor_init (EditorSpellCursor *cursor,
- const GtkTextIter *begin,
- const GtkTextIter *end,
- GtkTextTag *misspelled_tag);
-char *editor_spell_cursor_next_word (EditorSpellCursor *cursor);
-void editor_spell_cursor_tag (EditorSpellCursor *cursor);
-gboolean editor_spell_cursor_contains_tag (EditorSpellCursor *cursor,
- GtkTextTag *tag);
+EditorSpellCursor *editor_spell_cursor_new (GtkTextBuffer *buffer,
+ CjhTextRegion *region,
+ GtkTextTag *no_spell_check_tag);
+void editor_spell_cursor_free (EditorSpellCursor *cursor);
+gboolean editor_spell_cursor_next (EditorSpellCursor *cursor,
+ GtkTextIter *word_begin,
+ GtkTextIter *word_end);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (EditorSpellCursor, editor_spell_cursor_free)
G_END_DECLS
diff --git a/src/editor-text-buffer-spell-adapter.c b/src/editor-text-buffer-spell-adapter.c
index 94e05b3..c35df94 100644
--- a/src/editor-text-buffer-spell-adapter.c
+++ b/src/editor-text-buffer-spell-adapter.c
@@ -28,8 +28,8 @@
#include "editor-spell-language.h"
#include "editor-text-buffer-spell-adapter.h"
-#define UNCHECKED GSIZE_TO_POINTER(0)
-#define CHECKED GSIZE_TO_POINTER(1)
+#define RUN_UNCHECKED GSIZE_TO_POINTER(0)
+#define RUN_CHECKED GSIZE_TO_POINTER(1)
#define UPDATE_DELAY_MSECS 100
#define UPDATE_QUANTA_USEC (G_USEC_PER_SEC/1000L*2) /* 2 msec */
@@ -88,17 +88,25 @@ editor_text_buffer_spell_adapter_new (GtkTextBuffer *buffer,
NULL);
}
+static inline gboolean
+contains_iter (const GtkTextIter *begin,
+ const GtkTextIter *end,
+ const GtkTextIter *iter)
+{
+ return gtk_text_iter_compare (begin, iter) <= 0 &&
+ gtk_text_iter_compare (iter, end) <= 0;
+}
+
static gboolean
-scan_for_next_unchecked_cb (gsize offset,
- const CjhTextRegionRun *run,
- gpointer user_data)
+get_unchecked_start_cb (gsize offset,
+ const CjhTextRegionRun *run,
+ gpointer user_data)
{
- ScanForUnchecked *state = user_data;
+ gsize *pos = user_data;
- if (run->data == UNCHECKED)
+ if (run->data == RUN_UNCHECKED)
{
- state->offset = offset;
- state->found = TRUE;
+ *pos = offset;
return TRUE;
}
@@ -106,78 +114,81 @@ scan_for_next_unchecked_cb (gsize offset,
}
static gboolean
-scan_for_next_unchecked (CjhTextRegion *region,
- gsize begin,
- gsize end,
- gsize *position)
+get_unchecked_start (CjhTextRegion *region,
+ GtkTextBuffer *buffer,
+ GtkTextIter *iter)
{
- ScanForUnchecked state = {0};
- _cjh_text_region_foreach_in_range (region, begin, end, scan_for_next_unchecked_cb, &state);
- *position = state.offset;
- return state.found;
-}
-
-static inline gboolean
-contains_iter (const GtkTextIter *begin,
- const GtkTextIter *end,
- const GtkTextIter *iter)
-{
- return gtk_text_iter_compare (begin, iter) <= 0 &&
- gtk_text_iter_compare (iter, end) <= 0;
+ gsize pos = G_MAXSIZE;
+ _cjh_text_region_foreach (region, get_unchecked_start_cb, &pos);
+ if (pos == G_MAXSIZE)
+ return FALSE;
+ gtk_text_buffer_get_iter_at_offset (buffer, iter, pos);
+ return TRUE;
}
static gboolean
editor_text_buffer_spell_adapter_update_range (EditorTextBufferSpellAdapter *self,
- gsize begin_offset,
- gsize end_offset,
gint64 deadline)
{
- EditorSpellCursor cursor;
- GtkTextIter begin, end, insert;
- gsize position;
+ g_autoptr(EditorSpellCursor) cursor = NULL;
+ GtkTextMark *mark;
+ GtkTextIter insert, word_begin, word_end, last_match, begin;
gboolean ret = FALSE;
guint checked = 0;
- char *word;
g_assert (EDITOR_IS_TEXT_BUFFER_SPELL_ADAPTER (self));
- if (!scan_for_next_unchecked (self->region, begin_offset, end_offset, &position))
- return FALSE;
-
/* Ignore while we are loading or saving */
if (editor_document_get_busy (EDITOR_DOCUMENT (self->buffer)))
return TRUE;
- gtk_text_buffer_get_iter_at_mark (self->buffer, &insert,
- gtk_text_buffer_get_insert (self->buffer));
- gtk_text_buffer_get_iter_at_offset (self->buffer, &begin, position);
- gtk_text_buffer_get_iter_at_offset (self->buffer, &end, end_offset);
- gtk_text_buffer_remove_tag (self->buffer, self->tag, &begin, &end);
+ cursor = editor_spell_cursor_new (self->buffer, self->region, self->no_spell_check_tag);
+ mark = gtk_text_buffer_get_insert (self->buffer);
+ gtk_text_buffer_get_iter_at_mark (self->buffer, &insert, mark);
+
+ /* Get the first unchecked position so that we can remove the tag
+ * from it up to the first word match.
+ */
+ if (!get_unchecked_start (self->region, self->buffer, &begin))
+ {
+ _cjh_text_region_replace (self->region,
+ 0,
+ _cjh_text_region_get_length (self->region),
+ RUN_CHECKED);
+ return FALSE;
+ }
- editor_spell_cursor_init (&cursor, &begin, &end, self->tag);
- while ((word = editor_spell_cursor_next_word (&cursor)))
+ last_match = begin;
+ while (editor_spell_cursor_next (cursor, &word_begin, &word_end))
{
+ g_autofree char *word = gtk_text_iter_get_slice (&word_begin, &word_end);
+
checked++;
- if (!editor_spell_cursor_contains_tag (&cursor, self->no_spell_check_tag) &&
- !contains_iter (&cursor.word_begin, &cursor.word_end, &insert))
+ if (!contains_iter (&word_begin, &word_end, &insert) &&
+ !editor_spell_checker_check_word (self->checker, word, -1))
{
- if (!editor_spell_checker_check_word (self->checker, word, -1))
- editor_spell_cursor_tag (&cursor);
+ gtk_text_buffer_remove_tag (self->buffer, self->tag, &last_match, &word_end);
+ gtk_text_buffer_apply_tag (self->buffer, self->tag, &word_begin, &word_end);
+ last_match = word_end;
}
- g_free (word);
-
/* Check deadline every five words */
if (checked % 5 == 0 && deadline < g_get_monotonic_time ())
{
- end_offset = MAX (begin_offset, gtk_text_iter_get_offset (&cursor.word_end));
ret = TRUE;
break;
}
}
- _cjh_text_region_replace (self->region, begin_offset, end_offset - begin_offset, CHECKED);
+ /* Now remove from the last match to the end position */
+ if (!gtk_text_iter_equal (&word_end, &last_match))
+ gtk_text_buffer_remove_tag (self->buffer, self->tag, &last_match, &word_end);
+
+ _cjh_text_region_replace (self->region,
+ gtk_text_iter_get_offset (&begin),
+ gtk_text_iter_get_offset (&word_end) - gtk_text_iter_get_offset (&begin),
+ RUN_CHECKED);
return ret;
}
@@ -187,13 +198,11 @@ editor_text_buffer_spell_adapter_update (EditorTextBufferSpellAdapter *self)
{
gint64 deadline;
gboolean has_more;
- gsize length;
g_assert (EDITOR_IS_TEXT_BUFFER_SPELL_ADAPTER (self));
deadline = g_get_monotonic_time () + UPDATE_QUANTA_USEC;
- length = _cjh_text_region_get_length (self->region);
- has_more = editor_text_buffer_spell_adapter_update_range (self, 0, length, deadline);
+ has_more = editor_text_buffer_spell_adapter_update_range (self, deadline);
if (has_more)
return G_SOURCE_CONTINUE;
@@ -214,6 +223,10 @@ editor_text_buffer_spell_adapter_queue_update (EditorTextBufferSpellAdapter *sel
return;
}
+ /* TODO: We want an *initial* delay of UPDATE_DELAY_MSECS, but then after
+ * that we probably want something close to the widgets update
+ * interval so we make progress each frame.
+ */
if (self->update_source == 0)
self->update_source = g_timeout_add_full (G_PRIORITY_LOW,
UPDATE_DELAY_MSECS,
@@ -233,7 +246,7 @@ editor_text_buffer_spell_adapter_invalidate_all (EditorTextBufferSpellAdapter *s
if (length > 0)
{
- _cjh_text_region_replace (self->region, 0, length - 1, UNCHECKED);
+ _cjh_text_region_replace (self->region, 0, length - 1, RUN_UNCHECKED);
editor_text_buffer_spell_adapter_queue_update (self);
}
}
@@ -289,7 +302,7 @@ invalidate_tag_region_cb (EditorTextBufferSpellAdapter *self,
gsize begin_offset = gtk_text_iter_get_offset (begin);
gsize end_offset = gtk_text_iter_get_offset (end);
- _cjh_text_region_replace (self->region, begin_offset, end_offset - begin_offset, UNCHECKED);
+ _cjh_text_region_replace (self->region, begin_offset, end_offset - begin_offset, RUN_UNCHECKED);
editor_text_buffer_spell_adapter_queue_update (self);
}
}
@@ -313,7 +326,7 @@ editor_text_buffer_spell_adapter_set_buffer (EditorTextBufferSpellAdapter *self,
offset = gtk_text_iter_get_offset (&begin);
length = gtk_text_iter_get_offset (&end) - offset;
- _cjh_text_region_insert (self->region, offset, length, UNCHECKED);
+ _cjh_text_region_insert (self->region, offset, length, RUN_UNCHECKED);
self->tag = gtk_text_buffer_create_tag (buffer, NULL,
"underline", PANGO_UNDERLINE_ERROR,
@@ -531,7 +544,7 @@ editor_text_buffer_spell_adapter_set_checker (EditorTextBufferSpellAdapter *self
if (length > 0)
{
_cjh_text_region_remove (self->region, 0, length - 1);
- _cjh_text_region_insert (self->region, 0, length, UNCHECKED);
+ _cjh_text_region_insert (self->region, 0, length, RUN_UNCHECKED);
g_assert_cmpint (length, ==, _cjh_text_region_get_length (self->region));
}
@@ -558,7 +571,7 @@ editor_text_buffer_spell_adapter_insert_text (EditorTextBufferSpellAdapter *self
g_return_if_fail (EDITOR_IS_TEXT_BUFFER_SPELL_ADAPTER (self));
g_return_if_fail (length > 0);
- _cjh_text_region_insert (self->region, offset, length, UNCHECKED);
+ _cjh_text_region_insert (self->region, offset, length, RUN_UNCHECKED);
editor_text_buffer_spell_adapter_queue_update (self);
}
@@ -570,10 +583,10 @@ invalidate_surrounding (EditorTextBufferSpellAdapter *self,
g_assert (EDITOR_IS_TEXT_BUFFER_SPELL_ADAPTER (self));
if (offset)
- _cjh_text_region_replace (self->region, offset - 1, 1, UNCHECKED);
+ _cjh_text_region_replace (self->region, offset - 1, 1, RUN_UNCHECKED);
if (offset + 1 < _cjh_text_region_get_length (self->region))
- _cjh_text_region_replace (self->region, offset, 1, UNCHECKED);
+ _cjh_text_region_replace (self->region, offset, 1, RUN_UNCHECKED);
editor_text_buffer_spell_adapter_queue_update (self);
}
diff --git a/src/meson.build b/src/meson.build
index 8d2e640..2316460 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -91,3 +91,9 @@ editor = executable('gnome-text-editor', editor_sources + editor_enums + [build_
dependencies: editor_deps,
install: true,
)
+
+test_spell_cursor = executable('test-spell-cursor', 'test-spell-cursor.c',
+ dependencies: libgtk_dep,
+ include_directories: [include_directories('..')],
+)
+test('test-spell-cursor', test_spell_cursor)
diff --git a/src/test-spell-cursor.c b/src/test-spell-cursor.c
new file mode 100644
index 0000000..e729d73
--- /dev/null
+++ b/src/test-spell-cursor.c
@@ -0,0 +1,59 @@
+#include "editor-spell-cursor.c"
+#include "cjhtextregion.c"
+
+static const char *test_text = "this is a series of words";
+
+static char *
+next_word (EditorSpellCursor *cursor)
+{
+ GtkTextIter begin, end;
+
+ if (editor_spell_cursor_next (cursor, &begin, &end))
+ return gtk_text_iter_get_slice (&begin, &end);
+
+ return NULL;
+}
+
+static void
+test_cursor (void)
+{
+ g_autoptr(GtkTextBuffer) buffer = gtk_text_buffer_new (NULL);
+ CjhTextRegion *region = _cjh_text_region_new (NULL, NULL);
+ g_autoptr(EditorSpellCursor) cursor = editor_spell_cursor_new (buffer, region, NULL);
+ char *word;
+
+ gtk_text_buffer_set_text (buffer, test_text, -1);
+ _cjh_text_region_insert (region, 0, strlen (test_text), NULL);
+
+ word = next_word (cursor);
+ g_assert_cmpstr (word, ==, "this");
+
+ word = next_word (cursor);
+ g_assert_cmpstr (word, ==, "is");
+
+ word = next_word (cursor);
+ g_assert_cmpstr (word, ==, "a");
+
+ word = next_word (cursor);
+ g_assert_cmpstr (word, ==, "series");
+
+ word = next_word (cursor);
+ g_assert_cmpstr (word, ==, "of");
+
+ word = next_word (cursor);
+ g_assert_cmpstr (word, ==, "words");
+
+ word = next_word (cursor);
+ g_assert_cmpstr (word, ==, NULL);
+
+ _cjh_text_region_free (region);
+}
+
+int
+main (int argc,
+ char *argv[])
+{
+ g_test_init (&argc, &argv, NULL);
+ g_test_add_func ("/Spelling/Cursor/basic", test_cursor);
+ return g_test_run ();
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]