X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtktextiter.c;h=2b41c34cfbb3b84eb03249a564bc8bc102f357b5;hb=af0007d0d89309838fb55cc62c76793b574fa40a;hp=d508eda49dcb3c04399b9302c9e0b55c45b4ada0;hpb=0d5635b36613c0e5eca772e223001d1858433c28;p=~andy%2Fgtk diff --git a/gtk/gtktextiter.c b/gtk/gtktextiter.c index d508eda49..2b41c34cf 100644 --- a/gtk/gtktextiter.c +++ b/gtk/gtktextiter.c @@ -12,9 +12,7 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * License along with this library. If not, see . */ /* @@ -25,18 +23,32 @@ */ #define GTK_TEXT_USE_INTERNAL_UNSUPPORTED_API +#include "config.h" #include "gtktextiter.h" #include "gtktextbtree.h" #include "gtktextiterprivate.h" +#include "gtkintl.h" #include "gtkdebug.h" + #include -#include + + +/** + * SECTION:gtktextiter + * @Short_description: Text buffer iterator + * @Title: GtkTextIter + * + * You may wish to begin by reading the text widget + * conceptual overview which gives an overview of all the objects and data + * types related to the text widget and how they work together. + */ + #define FIX_OVERFLOWS(varname) if ((varname) == G_MININT) (varname) = G_MININT + 1 typedef struct _GtkTextRealIter GtkTextRealIter; -struct _GtkTextRealIter +struct G_GNUC_MAY_ALIAS _GtkTextRealIter { /* Always-valid information */ GtkTextBTree *tree; @@ -65,6 +77,10 @@ struct _GtkTextRealIter and ditto for char offsets. */ gint segment_byte_offset; gint segment_char_offset; + + /* padding */ + gint pad1; + gpointer pad2; }; /* These "set" functions should not assume any fields @@ -294,24 +310,12 @@ iter_init_from_char_offset (GtkTextIter *iter, return real; } -static inline void -invalidate_segment (GtkTextRealIter *iter) -{ - iter->segments_changed_stamp -= 1; -} - static inline void invalidate_char_index (GtkTextRealIter *iter) { iter->cached_char_index = -1; } -static inline void -invalidate_line_number (GtkTextRealIter *iter) -{ - iter->cached_line_number = -1; -} - static inline void adjust_char_index (GtkTextRealIter *iter, gint count) { @@ -326,28 +330,6 @@ adjust_line_number (GtkTextRealIter *iter, gint count) iter->cached_line_number += count; } -static inline void -adjust_char_offsets (GtkTextRealIter *iter, gint count) -{ - if (iter->line_char_offset >= 0) - { - iter->line_char_offset += count; - g_assert (iter->segment_char_offset >= 0); - iter->segment_char_offset += count; - } -} - -static inline void -adjust_byte_offsets (GtkTextRealIter *iter, gint count) -{ - if (iter->line_byte_offset >= 0) - { - iter->line_byte_offset += count; - g_assert (iter->segment_byte_offset >= 0); - iter->segment_byte_offset += count; - } -} - static inline void ensure_char_offsets (GtkTextRealIter *iter) { @@ -382,24 +364,24 @@ is_segment_start (GtkTextRealIter *real) return real->segment_byte_offset == 0 || real->segment_char_offset == 0; } -#if 1 +#ifdef G_ENABLE_DEBUG static void check_invariants (const GtkTextIter *iter) { - if (gtk_debug_flags & GTK_DEBUG_TEXT) + if (gtk_get_debug_flags () & GTK_DEBUG_TEXT) _gtk_text_iter_check (iter); } #else -#define check_invariants (x) +#define check_invariants(x) #endif /** * gtk_text_iter_get_buffer: * @iter: an iterator * - * Return the #GtkTextBuffer this iterator is associated with + * Returns the #GtkTextBuffer this iterator is associated with. * - * Return value: the buffer + * Return value: (transfer none): the buffer **/ GtkTextBuffer* gtk_text_iter_get_buffer (const GtkTextIter *iter) @@ -422,7 +404,7 @@ gtk_text_iter_get_buffer (const GtkTextIter *iter) * gtk_text_iter_copy: * @iter: an iterator * - * Create a dynamically-allocated copy of an iterator. This function + * Creates a dynamically-allocated copy of an iterator. This function * is not useful in applications, because iterators can be copied with a * simple assignment (GtkTextIter i = j;). The * function is used by language bindings. @@ -436,7 +418,7 @@ gtk_text_iter_copy (const GtkTextIter *iter) g_return_val_if_fail (iter != NULL, NULL); - new_iter = g_new (GtkTextIter, 1); + new_iter = g_slice_new (GtkTextIter); *new_iter = *iter; @@ -451,22 +433,47 @@ gtk_text_iter_copy (const GtkTextIter *iter) * is intended for use in language bindings, and is not * especially useful for applications, because iterators can * simply be allocated on the stack. - * **/ void gtk_text_iter_free (GtkTextIter *iter) { g_return_if_fail (iter != NULL); - g_free (iter); + g_slice_free (GtkTextIter, iter); +} + +/** + * gtk_text_iter_assign: + * @iter: a #GtkTextIter + * @other: another #GtkTextIter + * + * Assigns the value of @other to @iter. This function + * is not useful in applications, because iterators can be assigned + * with GtkTextIter i = j;. The + * function is used by language bindings. + * + * Since: 3.2 + **/ +void +gtk_text_iter_assign (GtkTextIter *iter, + const GtkTextIter *other) +{ + g_return_if_fail (iter != NULL); + g_return_if_fail (other != NULL); + + *iter = *other; } +G_DEFINE_BOXED_TYPE (GtkTextIter, gtk_text_iter, + gtk_text_iter_copy, + gtk_text_iter_free) + GtkTextLineSegment* _gtk_text_iter_get_indexable_segment (const GtkTextIter *iter) { GtkTextRealIter *real; - g_return_val_if_fail (iter != NULL, 0); + g_return_val_if_fail (iter != NULL, NULL); real = gtk_text_iter_make_real (iter); @@ -485,7 +492,7 @@ _gtk_text_iter_get_any_segment (const GtkTextIter *iter) { GtkTextRealIter *real; - g_return_val_if_fail (iter != NULL, 0); + g_return_val_if_fail (iter != NULL, NULL); real = gtk_text_iter_make_real (iter); @@ -544,7 +551,7 @@ _gtk_text_iter_get_text_line (const GtkTextIter *iter) { const GtkTextRealIter *real; - g_return_val_if_fail (iter != NULL, 0); + g_return_val_if_fail (iter != NULL, NULL); real = (const GtkTextRealIter*)iter; @@ -558,7 +565,7 @@ _gtk_text_iter_get_btree (const GtkTextIter *iter) { const GtkTextRealIter *real; - g_return_val_if_fail (iter != NULL, 0); + g_return_val_if_fail (iter != NULL, NULL); real = (const GtkTextRealIter*)iter; @@ -729,9 +736,11 @@ gtk_text_iter_get_visible_line_offset (const GtkTextIter *iter) ensure_char_offsets (real); check_invariants (iter); - + vis_offset = real->line_char_offset; + g_assert (vis_offset >= 0); + _gtk_text_btree_get_iter_at_line (real->tree, &pos, real->line, @@ -789,12 +798,14 @@ gtk_text_iter_get_visible_line_index (const GtkTextIter *iter) if (real == NULL) return 0; - ensure_char_offsets (real); + ensure_byte_offsets (real); check_invariants (iter); vis_offset = real->line_byte_offset; + g_assert (vis_offset >= 0); + _gtk_text_btree_get_iter_at_line (real->tree, &pos, real->line, @@ -834,7 +845,7 @@ gtk_text_iter_get_visible_line_index (const GtkTextIter *iter) * @iter: an iterator * * Returns the Unicode character at this iterator. (Equivalent to - * operator* on a C++ iterator.) If the iterator points at a + * operator* on a C++ iterator.) If the element at this iterator is a * non-character element, such as an image embedded in the buffer, the * Unicode "unknown" character 0xFFFC is returned. If invoked on * the end iterator, zero is returned; zero is not a valid Unicode character. @@ -980,11 +991,11 @@ gtk_text_iter_get_visible_text (const GtkTextIter *start, * gtk_text_iter_get_pixbuf: * @iter: an iterator * - * If the location pointed to by @iter contains a pixbuf, the pixbuf - * is returned (with no new reference count added). Otherwise, - * NULL is returned. + * If the element at @iter is a pixbuf, the pixbuf is returned + * (with no new reference count added). Otherwise, + * %NULL is returned. * - * Return value: the pixbuf at @iter + * Return value: (transfer none): the pixbuf at @iter **/ GdkPixbuf* gtk_text_iter_get_pixbuf (const GtkTextIter *iter) @@ -1010,11 +1021,11 @@ gtk_text_iter_get_pixbuf (const GtkTextIter *iter) * gtk_text_iter_get_child_anchor: * @iter: an iterator * - * If the location pointed to by @iter contains a child anchor, the + * If the location at @iter contains a child anchor, the * anchor is returned (with no new reference count added). Otherwise, - * NULL is returned. + * %NULL is returned. * - * Return value: the anchor at @iter + * Return value: (transfer none): the anchor at @iter **/ GtkTextChildAnchor* gtk_text_iter_get_child_anchor (const GtkTextIter *iter) @@ -1046,7 +1057,7 @@ gtk_text_iter_get_child_anchor (const GtkTextIter *iter) * can exist in the same place. The returned list is not in any * meaningful order. * - * Return value: list of #GtkTextMark + * Return value: (element-type GtkTextMark) (transfer container): list of #GtkTextMark **/ GSList* gtk_text_iter_get_marks (const GtkTextIter *iter) @@ -1083,16 +1094,16 @@ gtk_text_iter_get_marks (const GtkTextIter *iter) /** * gtk_text_iter_get_toggled_tags: * @iter: an iterator - * @toggled_on: TRUE to get toggled-on tags + * @toggled_on: %TRUE to get toggled-on tags * * Returns a list of #GtkTextTag that are toggled on or off at this - * point. (If @toggled_on is TRUE, the list contains tags that are + * point. (If @toggled_on is %TRUE, the list contains tags that are * toggled on.) If a tag is toggled on at @iter, then some non-empty * range of characters following @iter has that tag applied to it. If * a tag is toggled off, then some non-empty range following @iter * does not have the tag applied to it. * - * Return value: tags toggled at this point + * Return value: (element-type GtkTextTag) (transfer container): tags toggled at this point **/ GSList* gtk_text_iter_get_toggled_tags (const GtkTextIter *iter, @@ -1141,11 +1152,11 @@ gtk_text_iter_get_toggled_tags (const GtkTextIter *iter, /** * gtk_text_iter_begins_tag: * @iter: an iterator - * @tag: a #GtkTextTag, or NULL + * @tag: (allow-none): a #GtkTextTag, or %NULL * - * Returns TRUE if @tag is toggled on at exactly this point. If @tag - * is NULL, returns TRUE if any tag is toggled on at this point. Note - * that the gtk_text_iter_begins_tag () returns TRUE if @iter is the + * Returns %TRUE if @tag is toggled on at exactly this point. If @tag + * is %NULL, returns %TRUE if any tag is toggled on at this point. Note + * that the gtk_text_iter_begins_tag () returns %TRUE if @iter is the * start of the tagged range; * gtk_text_iter_has_tag () tells you whether an iterator is * within a tagged range. @@ -1187,11 +1198,11 @@ gtk_text_iter_begins_tag (const GtkTextIter *iter, /** * gtk_text_iter_ends_tag: * @iter: an iterator - * @tag: a #GtkTextTag, or NULL + * @tag: (allow-none): a #GtkTextTag, or %NULL * - * Returns TRUE if @tag is toggled off at exactly this point. If @tag - * is NULL, returns TRUE if any tag is toggled off at this point. Note - * that the gtk_text_iter_ends_tag () returns TRUE if @iter is the + * Returns %TRUE if @tag is toggled off at exactly this point. If @tag + * is %NULL, returns %TRUE if any tag is toggled off at this point. Note + * that the gtk_text_iter_ends_tag () returns %TRUE if @iter is the * end of the tagged range; * gtk_text_iter_has_tag () tells you whether an iterator is * within a tagged range. @@ -1234,7 +1245,7 @@ gtk_text_iter_ends_tag (const GtkTextIter *iter, /** * gtk_text_iter_toggles_tag: * @iter: an iterator - * @tag: a #GtkTextTag, or NULL + * @tag: (allow-none): a #GtkTextTag, or %NULL * * This is equivalent to (gtk_text_iter_begins_tag () || * gtk_text_iter_ends_tag ()), i.e. it tells you whether a range with @@ -1278,7 +1289,7 @@ gtk_text_iter_toggles_tag (const GtkTextIter *iter, * @iter: an iterator * @tag: a #GtkTextTag * - * Returns TRUE if @iter is within a range tagged with @tag. + * Returns %TRUE if @iter is within a range tagged with @tag. * * Return value: whether @iter is tagged with @tag **/ @@ -1319,8 +1330,8 @@ gtk_text_iter_has_tag (const GtkTextIter *iter, * priority (highest-priority tags are last). The #GtkTextTag in the * list don't have a reference added, but you have to free the list * itself. - * - * Return value: list of #GtkTextTag + * + * Return value: (element-type GtkTextTag) (transfer container): list of #GtkTextTag **/ GSList* gtk_text_iter_get_tags (const GtkTextIter *iter) @@ -1338,15 +1349,11 @@ gtk_text_iter_get_tags (const GtkTextIter *iter) /* No tags, use default style */ if (tags == NULL || tag_count == 0) { - if (tags) - g_free (tags); + g_free (tags); return NULL; } - /* Sort tags in ascending order of priority */ - _gtk_text_tag_array_sort (tags, tag_count); - retval = NULL; i = 0; while (i < tag_count) @@ -1449,7 +1456,7 @@ gtk_text_iter_can_insert (const GtkTextIter *iter, * * A convenience wrapper around gtk_text_iter_get_attributes (), * which returns the language in effect at @iter. If no tags affecting - * language * apply to @iter, the return value is identical to that of + * language apply to @iter, the return value is identical to that of * gtk_get_default_language (). * * Return value: language in effect at @iter @@ -1475,7 +1482,7 @@ gtk_text_iter_get_language (const GtkTextIter *iter) * gtk_text_iter_starts_line: * @iter: an iterator * - * Returns TRUE if @iter begins a paragraph, + * Returns %TRUE if @iter begins a paragraph, * i.e. if gtk_text_iter_get_line_offset () would return 0. * However this function is potentially more efficient than * gtk_text_iter_get_line_offset () because it doesn't have to compute @@ -1512,25 +1519,24 @@ gtk_text_iter_starts_line (const GtkTextIter *iter) * gtk_text_iter_ends_line: * @iter: an iterator * - * Returns TRUE if @iter points to the start of the paragraph delimiter - * characters for a line (delimiters will be either a newline, a - * carriage return, a carriage return followed by a newline, or a - * Unicode paragraph separator character). Note that an iterator pointing - * to the \n of a \r\n pair will not be counted as the end of a line, - * the line ends before the \r. + * Returns %TRUE if @iter points to the start of the paragraph + * delimiter characters for a line (delimiters will be either a + * newline, a carriage return, a carriage return followed by a + * newline, or a Unicode paragraph separator character). Note that an + * iterator pointing to the \n of a \r\n pair will not be counted as + * the end of a line, the line ends before the \r. The end iterator is + * considered to be at the end of a line, even though there are no + * paragraph delimiter chars there. * * Return value: whether @iter is at the end of a line **/ gboolean gtk_text_iter_ends_line (const GtkTextIter *iter) { - GtkTextRealIter *real; gunichar wc; g_return_val_if_fail (iter != NULL, FALSE); - real = gtk_text_iter_make_real (iter); - check_invariants (iter); /* Only one character has type G_UNICODE_PARAGRAPH_SEPARATOR in @@ -1540,14 +1546,27 @@ gtk_text_iter_ends_line (const GtkTextIter *iter) wc = gtk_text_iter_get_char (iter); - if (wc == '\r' || wc == PARAGRAPH_SEPARATOR) + if (wc == '\r' || wc == PARAGRAPH_SEPARATOR || wc == 0) /* wc == 0 is end iterator */ return TRUE; else if (wc == '\n') { + GtkTextIter tmp = *iter; + /* need to determine if a \r precedes the \n, in which case - * we aren't the end of the line + * we aren't the end of the line. + * Note however that if \r and \n are on different lines, they + * both are terminators. This for instance may happen after + * deleting some text: + + 1 some text\r delete 'a' 1 some text\r + 2 a\n ---------> 2 \n + 3 ... 3 ... + */ - GtkTextIter tmp = *iter; + + if (gtk_text_iter_get_line_offset (&tmp) == 0) + return TRUE; + if (!gtk_text_iter_backward_char (&tmp)) return TRUE; @@ -1561,7 +1580,7 @@ gtk_text_iter_ends_line (const GtkTextIter *iter) * gtk_text_iter_is_end: * @iter: an iterator * - * Returns TRUE if @iter is the end iterator, i.e. one past the last + * Returns %TRUE if @iter is the end iterator, i.e. one past the last * dereferenceable iterator in the buffer. gtk_text_iter_is_end () is * the most efficient way to check whether an iterator is the end * iterator. @@ -1601,7 +1620,7 @@ gtk_text_iter_is_end (const GtkTextIter *iter) * gtk_text_iter_is_start: * @iter: an iterator * - * Returns TRUE if @iter is the first iterator in the buffer, that is + * Returns %TRUE if @iter is the first iterator in the buffer, that is * if @iter has a character offset of 0. * * Return value: whether @iter is the first in the buffer @@ -1628,7 +1647,7 @@ gtk_text_iter_get_chars_in_line (const GtkTextIter *iter) gint count; GtkTextLineSegment *seg; - g_return_val_if_fail (iter != NULL, FALSE); + g_return_val_if_fail (iter != NULL, 0); real = gtk_text_iter_make_surreal (iter); @@ -1680,7 +1699,7 @@ gtk_text_iter_get_bytes_in_line (const GtkTextIter *iter) gint count; GtkTextLineSegment *seg; - g_return_val_if_fail (iter != NULL, FALSE); + g_return_val_if_fail (iter != NULL, 0); real = gtk_text_iter_make_surreal (iter); @@ -1718,7 +1737,7 @@ gtk_text_iter_get_bytes_in_line (const GtkTextIter *iter) /** * gtk_text_iter_get_attributes: * @iter: an iterator - * @values: a #GtkTextAttributes to be filled in + * @values: (out): a #GtkTextAttributes to be filled in * * Computes the effect of any tags applied to this spot in the * text. The @values parameter should be initialized to the default @@ -1744,15 +1763,11 @@ gtk_text_iter_get_attributes (const GtkTextIter *iter, /* No tags, use default style */ if (tags == NULL || tag_count == 0) { - if (tags) - g_free (tags); + g_free (tags); return FALSE; } - /* Sort tags in ascending order of priority */ - _gtk_text_tag_array_sort (tags, tag_count); - _gtk_text_attributes_fill_from_tags (values, tags, tag_count); @@ -1813,10 +1828,14 @@ forward_line_leaving_caches_unmodified (GtkTextRealIter *real) } } - +#if 0 /* The return value of this indicates WHETHER WE MOVED. * The return value of public functions indicates * (MOVEMENT OCCURRED && NEW ITER IS DEREFERENCEABLE) + * + * This function is currently unused, thus it is #if-0-ed. It is + * left here, since it's non-trivial code that might be useful in + * the future. */ static gboolean backward_line_leaving_caches_unmodified (GtkTextRealIter *real) @@ -1857,6 +1876,7 @@ backward_line_leaving_caches_unmodified (GtkTextRealIter *real) return FALSE; } } +#endif /* The return value indicates (MOVEMENT OCCURRED && NEW ITER IS * DEREFERENCEABLE) @@ -2005,14 +2025,17 @@ _gtk_text_iter_forward_indexable_segment (GtkTextIter *iter) } else { - /* End of buffer */ + /* End of buffer, but iter is still at start of last segment, + * not at the end iterator. We put it on the end iterator. + */ check_invariants (iter); g_assert (!_gtk_text_line_is_last (real->line, real->tree)); g_assert (_gtk_text_line_contains_end_iter (real->line, real->tree)); - if (!gtk_text_iter_is_end (iter)) - _gtk_text_btree_spew (_gtk_text_iter_get_btree (iter)); + + gtk_text_iter_forward_to_line_end (iter); + g_assert (gtk_text_iter_is_end (iter)); return FALSE; @@ -2101,7 +2124,7 @@ _gtk_text_iter_backward_indexable_segment (GtkTextIter *iter) * segment just before our current segment. */ g_assert (seg != real->segment); - while (seg != real->segment) + do { prev_seg = seg; prev_any_seg = any_seg; @@ -2111,6 +2134,7 @@ _gtk_text_iter_backward_indexable_segment (GtkTextIter *iter) while (seg->char_count == 0) seg = seg->next; } + while (seg != real->segment); g_assert (prev_seg != NULL); g_assert (prev_any_seg != NULL); @@ -2181,10 +2205,10 @@ _gtk_text_iter_backward_indexable_segment (GtkTextIter *iter) * gtk_text_iter_forward_char () may actually move onto an image instead * of a character, if you have images in your buffer. If @iter is the * end iterator or one character before it, @iter will now point at - * the end iterator, and gtk_text_iter_forward_char () returns FALSE for + * the end iterator, and gtk_text_iter_forward_char () returns %FALSE for * convenience when writing loops. * - * Return value: whether the new position is the end iterator + * Return value: whether @iter moved and is dereferenceable **/ gboolean gtk_text_iter_forward_char (GtkTextIter *iter) @@ -2208,9 +2232,9 @@ gtk_text_iter_forward_char (GtkTextIter *iter) * gtk_text_iter_backward_char: * @iter: an iterator * - * Moves backward by one character offset. Returns TRUE if movement + * Moves backward by one character offset. Returns %TRUE if movement * was possible; if @iter was the first in the buffer (character - * offset 0), gtk_text_iter_backward_char () returns FALSE for convenience when + * offset 0), gtk_text_iter_backward_char () returns %FALSE for convenience when * writing loops. * * Return value: whether movement was possible @@ -2250,7 +2274,7 @@ gtk_text_iter_backward_char (GtkTextIter *iter) * buffer). The return value indicates whether the new position of * @iter is different from its original position, and dereferenceable * (the last iterator in the buffer is not dereferenceable). If @count - * is 0, the function does nothing and returns FALSE. + * is 0, the function does nothing and returns %FALSE. * * Return value: whether @iter moved and is dereferenceable **/ @@ -2320,8 +2344,8 @@ gtk_text_iter_forward_chars (GtkTextIter *iter, gint count) * past the start or end of the buffer, moves to the start or end of * the buffer). The return value indicates whether the iterator moved * onto a dereferenceable position; if the iterator didn't move, or - * moved onto the end iterator, then FALSE is returned. If @count is 0, - * the function does nothing and returns FALSE. + * moved onto the end iterator, then %FALSE is returned. If @count is 0, + * the function does nothing and returns %FALSE. * * Return value: whether @iter moved and is dereferenceable * @@ -2347,34 +2371,35 @@ gtk_text_iter_backward_chars (GtkTextIter *iter, gint count) ensure_char_offsets (real); check_invariants (iter); - if (count <= real->segment_char_offset) + /* <, not <=, because if count == segment_char_offset + * we're going to the front of the segment and the any_segment + * might change + */ + if (count < real->segment_char_offset) { /* Optimize the within-segment case */ g_assert (real->segment->char_count > 0); g_assert (real->segment->type == >k_text_char_type); - real->segment_char_offset -= count; - g_assert (real->segment_char_offset >= 0); - if (real->line_byte_offset >= 0) { + const char *p; gint new_byte_offset; - gint i; - - new_byte_offset = 0; - i = 0; - while (i < real->segment_char_offset) - { - const char * start = real->segment->body.chars + new_byte_offset; - new_byte_offset += g_utf8_next_char (start) - start; - ++i; - } + /* if in the last fourth of the segment walk backwards */ + if (count < real->segment_char_offset / 4) + p = g_utf8_offset_to_pointer (real->segment->body.chars + real->segment_byte_offset, + -count); + else + p = g_utf8_offset_to_pointer (real->segment->body.chars, + real->segment_char_offset - count); + new_byte_offset = p - real->segment->body.chars; real->line_byte_offset -= (real->segment_byte_offset - new_byte_offset); real->segment_byte_offset = new_byte_offset; } + real->segment_char_offset -= count; real->line_char_offset -= count; adjust_char_index (real, 0 - count); @@ -2402,6 +2427,7 @@ gtk_text_iter_backward_chars (GtkTextIter *iter, gint count) new_char_index = current_char_index - count; if (new_char_index < 0) new_char_index = 0; + gtk_text_iter_set_offset (iter, new_char_index); check_invariants (iter); @@ -2446,7 +2472,7 @@ gtk_text_iter_forward_text_chars (GtkTextIter *iter, } /** - * gtk_text_iter_forward_text_chars: + * gtk_text_iter_backward_text_chars: * @iter: a #GtkTextIter * @count: number of chars to move * @@ -2470,10 +2496,10 @@ gtk_text_iter_backward_text_chars (GtkTextIter *iter, * gtk_text_iter_forward_line: * @iter: an iterator * - * Moves @iter to the start of the next line. Returns TRUE if there - * was a next line to move to, and FALSE if @iter was simply moved to - * the end of the buffer and is now not dereferenceable, or if @iter was - * already at the end of the buffer. + * Moves @iter to the start of the next line. If the iter is already on the + * last line of the buffer, moves the iter to the end of the current line. + * If after the operation, the iter is at the end of the buffer and not + * dereferencable, returns %FALSE. Otherwise, returns %TRUE. * * Return value: whether @iter can be dereferenced **/ @@ -2519,11 +2545,11 @@ gtk_text_iter_forward_line (GtkTextIter *iter) * gtk_text_iter_backward_line: * @iter: an iterator * - * Moves @iter to the start of the previous line. Returns TRUE if + * Moves @iter to the start of the previous line. Returns %TRUE if * @iter could be moved; i.e. if @iter was at character offset 0, this - * function returns FALSE. Therefore if @iter was already on line 0, + * function returns %FALSE. Therefore if @iter was already on line 0, * but not at the start of the line, @iter is snapped to the start of - * the line and the function returns TRUE. (Note that this implies that + * the line and the function returns %TRUE. (Note that this implies that * in a loop calling this function, the line number may not change on * every iteration, if your first iteration is on line 0.) * @@ -2600,8 +2626,8 @@ gtk_text_iter_backward_line (GtkTextIter *iter) * past the start or end of the buffer, moves to the start or end of * the buffer). The return value indicates whether the iterator moved * onto a dereferenceable position; if the iterator didn't move, or - * moved onto the end iterator, then FALSE is returned. If @count is 0, - * the function does nothing and returns FALSE. If @count is negative, + * moved onto the end iterator, then %FALSE is returned. If @count is 0, + * the function does nothing and returns %FALSE. If @count is negative, * moves backward by 0 - @count lines. * * Return value: whether @iter moved and is dereferenceable @@ -2651,8 +2677,8 @@ gtk_text_iter_forward_lines (GtkTextIter *iter, gint count) * past the start or end of the buffer, moves to the start or end of * the buffer). The return value indicates whether the iterator moved * onto a dereferenceable position; if the iterator didn't move, or - * moved onto the end iterator, then FALSE is returned. If @count is 0, - * the function does nothing and returns FALSE. If @count is negative, + * moved onto the end iterator, then %FALSE is returned. If @count is 0, + * the function does nothing and returns %FALSE. If @count is negative, * moves forward by 0 - @count lines. * * Return value: whether @iter moved and is dereferenceable @@ -2682,6 +2708,162 @@ gtk_text_iter_backward_lines (GtkTextIter *iter, gint count) } } +/** + * gtk_text_iter_forward_visible_line: + * @iter: an iterator + * + * Moves @iter to the start of the next visible line. Returns %TRUE if there + * was a next line to move to, and %FALSE if @iter was simply moved to + * the end of the buffer and is now not dereferenceable, or if @iter was + * already at the end of the buffer. + * + * Return value: whether @iter can be dereferenced + * + * Since: 2.8 + **/ +gboolean +gtk_text_iter_forward_visible_line (GtkTextIter *iter) +{ + while (gtk_text_iter_forward_line (iter)) + { + if (!_gtk_text_btree_char_is_invisible (iter)) + return TRUE; + else + { + do + { + if (!gtk_text_iter_forward_char (iter)) + return FALSE; + + if (!_gtk_text_btree_char_is_invisible (iter)) + return TRUE; + } + while (!gtk_text_iter_ends_line (iter)); + } + } + + return FALSE; +} + +/** + * gtk_text_iter_backward_visible_line: + * @iter: an iterator + * + * Moves @iter to the start of the previous visible line. Returns %TRUE if + * @iter could be moved; i.e. if @iter was at character offset 0, this + * function returns %FALSE. Therefore if @iter was already on line 0, + * but not at the start of the line, @iter is snapped to the start of + * the line and the function returns %TRUE. (Note that this implies that + * in a loop calling this function, the line number may not change on + * every iteration, if your first iteration is on line 0.) + * + * Return value: whether @iter moved + * + * Since: 2.8 + **/ +gboolean +gtk_text_iter_backward_visible_line (GtkTextIter *iter) +{ + while (gtk_text_iter_backward_line (iter)) + { + if (!_gtk_text_btree_char_is_invisible (iter)) + return TRUE; + else + { + do + { + if (!gtk_text_iter_backward_char (iter)) + return FALSE; + + if (!_gtk_text_btree_char_is_invisible (iter)) + return TRUE; + } + while (!gtk_text_iter_starts_line (iter)); + } + } + + return FALSE; +} + +/** + * gtk_text_iter_forward_visible_lines: + * @iter: a #GtkTextIter + * @count: number of lines to move forward + * + * Moves @count visible lines forward, if possible (if @count would move + * past the start or end of the buffer, moves to the start or end of + * the buffer). The return value indicates whether the iterator moved + * onto a dereferenceable position; if the iterator didn't move, or + * moved onto the end iterator, then %FALSE is returned. If @count is 0, + * the function does nothing and returns %FALSE. If @count is negative, + * moves backward by 0 - @count lines. + * + * Return value: whether @iter moved and is dereferenceable + * + * Since: 2.8 + **/ +gboolean +gtk_text_iter_forward_visible_lines (GtkTextIter *iter, + gint count) +{ + FIX_OVERFLOWS (count); + + if (count < 0) + return gtk_text_iter_backward_visible_lines (iter, 0 - count); + else if (count == 0) + return FALSE; + else if (count == 1) + { + check_invariants (iter); + return gtk_text_iter_forward_visible_line (iter); + } + else + { + while (gtk_text_iter_forward_visible_line (iter) && count > 0) + count--; + return count == 0; + } +} + +/** + * gtk_text_iter_backward_visible_lines: + * @iter: a #GtkTextIter + * @count: number of lines to move backward + * + * Moves @count visible lines backward, if possible (if @count would move + * past the start or end of the buffer, moves to the start or end of + * the buffer). The return value indicates whether the iterator moved + * onto a dereferenceable position; if the iterator didn't move, or + * moved onto the end iterator, then %FALSE is returned. If @count is 0, + * the function does nothing and returns %FALSE. If @count is negative, + * moves forward by 0 - @count lines. + * + * Return value: whether @iter moved and is dereferenceable + * + * Since: 2.8 + **/ +gboolean +gtk_text_iter_backward_visible_lines (GtkTextIter *iter, + gint count) +{ + FIX_OVERFLOWS (count); + + if (count < 0) + return gtk_text_iter_forward_visible_lines (iter, 0 - count); + else if (count == 0) + return FALSE; + else if (count == 1) + { + return gtk_text_iter_backward_visible_line (iter); + } + else + { + while (gtk_text_iter_backward_visible_line (iter) && count > 0) + count--; + return count == 0; + } +} + typedef gboolean (* FindLogAttrFunc) (const PangoLogAttr *attrs, gint offset, gint min_offset, @@ -2767,7 +2949,10 @@ inside_word_func (const PangoLogAttr *attrs, !(attrs[offset].is_word_start || attrs[offset].is_word_end)) --offset; - return attrs[offset].is_word_start; + if (offset >= 0) + return attrs[offset].is_word_start; + else + return FALSE; } /* Sentence funcs */ @@ -2869,7 +3054,7 @@ test_log_attrs (const GtkTextIter *iter, * for one past the end */ - if (offset <= char_len) + if (attrs && offset <= char_len) result = (* func) (attrs, offset, 0, char_len); return result; @@ -2960,6 +3145,64 @@ find_by_log_attrs (GtkTextIter *iter, } } +static gboolean +find_visible_by_log_attrs (GtkTextIter *iter, + FindLogAttrFunc func, + gboolean forward, + gboolean already_moved_initially) +{ + GtkTextIter pos; + + g_return_val_if_fail (iter != NULL, FALSE); + + pos = *iter; + + while (find_by_log_attrs (&pos, func, forward, already_moved_initially)) + { + if (!_gtk_text_btree_char_is_invisible (&pos)) + { + *iter = pos; + return TRUE; + } + } + + return FALSE; +} + +typedef gboolean (* OneStepFunc) (GtkTextIter *iter); +typedef gboolean (* MultipleStepFunc) (GtkTextIter *iter, gint count); + +static gboolean +move_multiple_steps (GtkTextIter *iter, + gint count, + OneStepFunc step_forward, + MultipleStepFunc n_steps_backward) +{ + g_return_val_if_fail (iter != NULL, FALSE); + + FIX_OVERFLOWS (count); + + if (count == 0) + return FALSE; + + if (count < 0) + return n_steps_backward (iter, -count); + + if (!step_forward (iter)) + return FALSE; + --count; + + while (count > 0) + { + if (!step_forward (iter)) + break; + --count; + } + + return !gtk_text_iter_is_end (iter); +} + + /** * gtk_text_iter_forward_word_end: * @iter: a #GtkTextIter @@ -2982,7 +3225,7 @@ gtk_text_iter_forward_word_end (GtkTextIter *iter) * gtk_text_iter_backward_word_start: * @iter: a #GtkTextIter * - * Moves backward to the next word start. (If @iter is currently on a + * Moves backward to the previous word start. (If @iter is currently on a * word start, moves backward to the next one after that.) Word breaks * are determined by Pango and should be correct for nearly any * language (if not, the correct fix would be to the Pango word break @@ -3013,35 +3256,17 @@ gboolean gtk_text_iter_forward_word_ends (GtkTextIter *iter, gint count) { - g_return_val_if_fail (iter != NULL, FALSE); - - FIX_OVERFLOWS (count); - - if (count == 0) - return FALSE; - - if (count < 0) - return gtk_text_iter_backward_word_starts (iter, -count); - - if (!gtk_text_iter_forward_word_end (iter)) - return FALSE; - --count; - - while (count > 0) - { - if (!gtk_text_iter_forward_word_end (iter)) - break; - --count; - } - return TRUE; + return move_multiple_steps (iter, count, + gtk_text_iter_forward_word_end, + gtk_text_iter_backward_word_starts); } /** - * gtk_text_iter_backward_word_starts + * gtk_text_iter_backward_word_starts: * @iter: a #GtkTextIter * @count: number of times to move * - * Calls gtk_text_iter_backward_word_starts() up to @count times. + * Calls gtk_text_iter_backward_word_start() up to @count times. * * Return value: %TRUE if @iter moved and is not the end iterator **/ @@ -3049,24 +3274,89 @@ gboolean gtk_text_iter_backward_word_starts (GtkTextIter *iter, gint count) { - g_return_val_if_fail (iter != NULL, FALSE); + return move_multiple_steps (iter, count, + gtk_text_iter_backward_word_start, + gtk_text_iter_forward_word_ends); +} - FIX_OVERFLOWS (count); - - if (count < 0) - return gtk_text_iter_forward_word_ends (iter, -count); +/** + * gtk_text_iter_forward_visible_word_end: + * @iter: a #GtkTextIter + * + * Moves forward to the next visible word end. (If @iter is currently on a + * word end, moves forward to the next one after that.) Word breaks + * are determined by Pango and should be correct for nearly any + * language (if not, the correct fix would be to the Pango word break + * algorithms). + * + * Return value: %TRUE if @iter moved and is not the end iterator + * + * Since: 2.4 + **/ +gboolean +gtk_text_iter_forward_visible_word_end (GtkTextIter *iter) +{ + return find_visible_by_log_attrs (iter, find_word_end_func, TRUE, FALSE); +} - if (!gtk_text_iter_backward_word_start (iter)) - return FALSE; - --count; +/** + * gtk_text_iter_backward_visible_word_start: + * @iter: a #GtkTextIter + * + * Moves backward to the previous visible word start. (If @iter is currently + * on a word start, moves backward to the next one after that.) Word breaks + * are determined by Pango and should be correct for nearly any + * language (if not, the correct fix would be to the Pango word break + * algorithms). + * + * Return value: %TRUE if @iter moved and is not the end iterator + * + * Since: 2.4 + **/ +gboolean +gtk_text_iter_backward_visible_word_start (GtkTextIter *iter) +{ + return find_visible_by_log_attrs (iter, find_word_start_func, FALSE, FALSE); +} - while (count > 0) - { - if (!gtk_text_iter_backward_word_start (iter)) - break; - --count; - } - return TRUE; +/** + * gtk_text_iter_forward_visible_word_ends: + * @iter: a #GtkTextIter + * @count: number of times to move + * + * Calls gtk_text_iter_forward_visible_word_end() up to @count times. + * + * Return value: %TRUE if @iter moved and is not the end iterator + * + * Since: 2.4 + **/ +gboolean +gtk_text_iter_forward_visible_word_ends (GtkTextIter *iter, + gint count) +{ + return move_multiple_steps (iter, count, + gtk_text_iter_forward_visible_word_end, + gtk_text_iter_backward_visible_word_starts); +} + +/** + * gtk_text_iter_backward_visible_word_starts: + * @iter: a #GtkTextIter + * @count: number of times to move + * + * Calls gtk_text_iter_backward_visible_word_start() up to @count times. + * + * Return value: %TRUE if @iter moved and is not the end iterator + * + * Since: 2.4 + **/ +gboolean +gtk_text_iter_backward_visible_word_starts (GtkTextIter *iter, + gint count) +{ + return move_multiple_steps (iter, count, + gtk_text_iter_backward_visible_word_start, + gtk_text_iter_forward_visible_word_ends); } /** @@ -3194,7 +3484,7 @@ gtk_text_iter_forward_sentence_end (GtkTextIter *iter) * gtk_text_iter_backward_sentence_start: * @iter: a #GtkTextIter * - * Moves backward to the next sentence start; if @iter is already at + * Moves backward to the previous sentence start; if @iter is already at * the start of a sentence, moves backward to the next one. Sentence * boundaries are determined by Pango and should be correct for nearly * any language (if not, the correct fix would be to the Pango text @@ -3226,25 +3516,9 @@ gboolean gtk_text_iter_forward_sentence_ends (GtkTextIter *iter, gint count) { - g_return_val_if_fail (iter != NULL, FALSE); - - if (count == 0) - return FALSE; - - if (count < 0) - return gtk_text_iter_backward_sentence_starts (iter, -count); - - if (!gtk_text_iter_forward_sentence_end (iter)) - return FALSE; - --count; - - while (count > 0) - { - if (!gtk_text_iter_forward_sentence_end (iter)) - break; - --count; - } - return TRUE; + return move_multiple_steps (iter, count, + gtk_text_iter_forward_sentence_end, + gtk_text_iter_backward_sentence_starts); } /** @@ -3262,22 +3536,9 @@ gboolean gtk_text_iter_backward_sentence_starts (GtkTextIter *iter, gint count) { - g_return_val_if_fail (iter != NULL, FALSE); - - if (count < 0) - return gtk_text_iter_forward_sentence_ends (iter, -count); - - if (!gtk_text_iter_backward_sentence_start (iter)) - return FALSE; - --count; - - while (count > 0) - { - if (!gtk_text_iter_backward_sentence_start (iter)) - break; - --count; - } - return TRUE; + return move_multiple_steps (iter, count, + gtk_text_iter_backward_sentence_start, + gtk_text_iter_forward_sentence_ends); } static gboolean @@ -3358,7 +3619,7 @@ gtk_text_iter_forward_cursor_position (GtkTextIter *iter) * * Like gtk_text_iter_forward_cursor_position(), but moves backward. * - * Return value: %TRUE if we moved and the new position is dereferenceable + * Return value: %TRUE if we moved **/ gboolean gtk_text_iter_backward_cursor_position (GtkTextIter *iter) @@ -3380,27 +3641,9 @@ gboolean gtk_text_iter_forward_cursor_positions (GtkTextIter *iter, gint count) { - g_return_val_if_fail (iter != NULL, FALSE); - - FIX_OVERFLOWS (count); - - if (count == 0) - return FALSE; - - if (count < 0) - return gtk_text_iter_backward_cursor_positions (iter, -count); - - if (!gtk_text_iter_forward_cursor_position (iter)) - return FALSE; - --count; - - while (count > 0) - { - if (!gtk_text_iter_forward_cursor_position (iter)) - break; - --count; - } - return TRUE; + return move_multiple_steps (iter, count, + gtk_text_iter_forward_cursor_position, + gtk_text_iter_backward_cursor_positions); } /** @@ -3417,27 +3660,85 @@ gboolean gtk_text_iter_backward_cursor_positions (GtkTextIter *iter, gint count) { - g_return_val_if_fail (iter != NULL, FALSE); + return move_multiple_steps (iter, count, + gtk_text_iter_backward_cursor_position, + gtk_text_iter_forward_cursor_positions); +} - FIX_OVERFLOWS (count); - - if (count == 0) - return FALSE; +/** + * gtk_text_iter_forward_visible_cursor_position: + * @iter: a #GtkTextIter + * + * Moves @iter forward to the next visible cursor position. See + * gtk_text_iter_forward_cursor_position() for details. + * + * Return value: %TRUE if we moved and the new position is dereferenceable + * + * Since: 2.4 + **/ +gboolean +gtk_text_iter_forward_visible_cursor_position (GtkTextIter *iter) +{ + return find_visible_by_log_attrs (iter, find_forward_cursor_pos_func, TRUE, FALSE); +} - if (count < 0) - return gtk_text_iter_forward_cursor_positions (iter, -count); - - if (!gtk_text_iter_backward_cursor_position (iter)) - return FALSE; - --count; +/** + * gtk_text_iter_backward_visible_cursor_position: + * @iter: a #GtkTextIter + * + * Moves @iter forward to the previous visible cursor position. See + * gtk_text_iter_backward_cursor_position() for details. + * + * Return value: %TRUE if we moved and the new position is dereferenceable + * + * Since: 2.4 + **/ +gboolean +gtk_text_iter_backward_visible_cursor_position (GtkTextIter *iter) +{ + return find_visible_by_log_attrs (iter, find_backward_cursor_pos_func, FALSE, FALSE); +} - while (count > 0) - { - if (!gtk_text_iter_backward_cursor_position (iter)) - break; - --count; - } - return TRUE; +/** + * gtk_text_iter_forward_visible_cursor_positions: + * @iter: a #GtkTextIter + * @count: number of positions to move + * + * Moves up to @count visible cursor positions. See + * gtk_text_iter_forward_cursor_position() for details. + * + * Return value: %TRUE if we moved and the new position is dereferenceable + * + * Since: 2.4 + **/ +gboolean +gtk_text_iter_forward_visible_cursor_positions (GtkTextIter *iter, + gint count) +{ + return move_multiple_steps (iter, count, + gtk_text_iter_forward_visible_cursor_position, + gtk_text_iter_backward_visible_cursor_positions); +} + +/** + * gtk_text_iter_backward_visible_cursor_positions: + * @iter: a #GtkTextIter + * @count: number of positions to move + * + * Moves up to @count visible cursor positions. See + * gtk_text_iter_backward_cursor_position() for details. + * + * Return value: %TRUE if we moved and the new position is dereferenceable + * + * Since: 2.4 + **/ +gboolean +gtk_text_iter_backward_visible_cursor_positions (GtkTextIter *iter, + gint count) +{ + return move_multiple_steps (iter, count, + gtk_text_iter_backward_visible_cursor_position, + gtk_text_iter_forward_visible_cursor_positions); } /** @@ -3561,6 +3862,8 @@ gtk_text_iter_set_visible_line_offset (GtkTextIter *iter, g_return_if_fail (iter != NULL); + gtk_text_iter_set_line_offset (iter, 0); + pos = *iter; /* For now we use a ludicrously slow implementation */ @@ -3582,12 +3885,6 @@ gtk_text_iter_set_visible_line_offset (GtkTextIter *iter, gtk_text_iter_forward_line (iter); } -static gint -bytes_in_char (GtkTextIter *iter) -{ - return g_unichar_to_utf8 (gtk_text_iter_get_char (iter), NULL); -} - /** * gtk_text_iter_set_visible_line_index: * @iter: a #GtkTextIter @@ -3598,36 +3895,51 @@ bytes_in_char (GtkTextIter *iter) * in the index. **/ void -gtk_text_iter_set_visible_line_index (GtkTextIter *iter, - gint byte_on_line) +gtk_text_iter_set_visible_line_index (GtkTextIter *iter, + gint byte_on_line) { - gint bytes_seen = 0; + GtkTextRealIter *real; + gint offset = 0; GtkTextIter pos; - - g_return_if_fail (iter != NULL); + GtkTextLineSegment *seg; + g_return_if_fail (iter != NULL); + + gtk_text_iter_set_line_offset (iter, 0); + pos = *iter; - /* For now we use a ludicrously slow implementation */ - while (bytes_seen < byte_on_line) + real = gtk_text_iter_make_real (&pos); + + if (real == NULL) + return; + + ensure_byte_offsets (real); + + check_invariants (&pos); + + seg = _gtk_text_iter_get_indexable_segment (&pos); + + while (seg != NULL && byte_on_line > 0) { if (!_gtk_text_btree_char_is_invisible (&pos)) - bytes_seen += bytes_in_char (&pos); - - if (!gtk_text_iter_forward_char (&pos)) - break; + { + if (byte_on_line < seg->byte_count) + { + iter_set_from_byte_offset (real, real->line, offset + byte_on_line); + byte_on_line = 0; + break; + } + else + byte_on_line -= seg->byte_count; + } - if (bytes_seen >= byte_on_line) - break; + offset += seg->byte_count; + _gtk_text_iter_forward_indexable_segment (&pos); + seg = _gtk_text_iter_get_indexable_segment (&pos); } - if (bytes_seen > byte_on_line) - g_warning ("%s: Incorrect visible byte index %d falls in the middle of a UTF-8 " - "character; this will crash the text buffer. " - "Byte indexes must refer to the start of a character.", - G_STRLOC, byte_on_line); - - if (_gtk_text_iter_get_text_line (&pos) == _gtk_text_iter_get_text_line (iter)) + if (byte_on_line == 0) *iter = pos; else gtk_text_iter_forward_line (iter); @@ -3677,7 +3989,6 @@ gtk_text_iter_set_line (GtkTextIter *iter, * * Sets @iter to point to @char_offset. @char_offset counts from the start * of the entire text buffer, starting with 0. - * **/ void gtk_text_iter_set_offset (GtkTextIter *iter, @@ -3721,7 +4032,6 @@ gtk_text_iter_set_offset (GtkTextIter *iter, * Moves @iter forward to the "end iterator," which points one past the last * valid character in the buffer. gtk_text_iter_get_char() called on the * end iterator returns 0, which is convenient for writing loops. - * **/ void gtk_text_iter_forward_to_end (GtkTextIter *iter) @@ -3752,11 +4062,17 @@ find_paragraph_delimiter_for_line (GtkTextIter *iter) GtkTextIter end; end = *iter; - /* if we aren't on the last line, go forward to start of next line, then scan - * back for the delimiters on the previous line - */ - if (gtk_text_iter_forward_line (&end)) + if (_gtk_text_line_contains_end_iter (_gtk_text_iter_get_text_line (&end), + _gtk_text_iter_get_btree (&end))) { + gtk_text_iter_forward_to_end (&end); + } + else + { + /* if we aren't on the last line, go forward to start of next line, then scan + * back for the delimiters on the previous line + */ + gtk_text_iter_forward_line (&end); gtk_text_iter_backward_char (&end); while (!gtk_text_iter_ends_line (&end)) gtk_text_iter_backward_char (&end); @@ -3796,7 +4112,7 @@ gtk_text_iter_forward_to_line_end (GtkTextIter *iter) { /* Move to end of this line. */ gtk_text_iter_set_line_offset (iter, new_offset); - return TRUE; + return !gtk_text_iter_is_end (iter); } else { @@ -3808,7 +4124,7 @@ gtk_text_iter_forward_to_line_end (GtkTextIter *iter) */ if (!gtk_text_iter_ends_line (iter)) gtk_text_iter_forward_to_line_end (iter); - return TRUE; + return !gtk_text_iter_is_end (iter); } else return FALSE; @@ -3818,12 +4134,12 @@ gtk_text_iter_forward_to_line_end (GtkTextIter *iter) /** * gtk_text_iter_forward_to_tag_toggle: * @iter: a #GtkTextIter - * @tag: a #GtkTextTag, or NULL + * @tag: (allow-none): a #GtkTextTag, or %NULL * * Moves forward to the next toggle (on or off) of the * #GtkTextTag @tag, or to the next toggle of any tag if - * @tag is NULL. If no matching tag toggles are found, - * returns FALSE, otherwise TRUE. Does not return toggles + * @tag is %NULL. If no matching tag toggles are found, + * returns %FALSE, otherwise %TRUE. Does not return toggles * located at @iter, only toggles after @iter. Sets @iter to * the location of the toggle, or to the end of the buffer * if no toggle is found. @@ -3900,12 +4216,12 @@ gtk_text_iter_forward_to_tag_toggle (GtkTextIter *iter, /** * gtk_text_iter_backward_to_tag_toggle: * @iter: a #GtkTextIter - * @tag: a #GtkTextTag, or NULL + * @tag: (allow-none): a #GtkTextTag, or %NULL * * Moves backward to the next toggle (on or off) of the * #GtkTextTag @tag, or to the next toggle of any tag if - * @tag is NULL. If no matching tag toggles are found, - * returns FALSE, otherwise TRUE. Does not return toggles + * @tag is %NULL. If no matching tag toggles are found, + * returns %FALSE, otherwise %TRUE. Does not return toggles * located at @iter, only toggles before @iter. Sets @iter * to the location of the toggle, or the start of the buffer * if no toggle is found. @@ -4013,9 +4329,9 @@ matches_pred (GtkTextIter *iter, /** * gtk_text_iter_forward_find_char: * @iter: a #GtkTextIter - * @pred: a function to be called on each character + * @pred: (scope call): a function to be called on each character * @user_data: user data for @pred - * @limit: search limit, or %NULL for none + * @limit: (allow-none): search limit, or %NULL for none * * Advances @iter, calling @pred on each character. If * @pred returns %TRUE, returns %TRUE and stops scanning. @@ -4051,9 +4367,9 @@ gtk_text_iter_forward_find_char (GtkTextIter *iter, /** * gtk_text_iter_backward_find_char: * @iter: a #GtkTextIter - * @pred: function to be called on each character + * @pred: (scope call): function to be called on each character * @user_data: user data for @pred - * @limit: search limit, or %NULL for none + * @limit: (allow-none): search limit, or %NULL for none * * Same as gtk_text_iter_forward_find_char(), but goes backward from @iter. * @@ -4087,7 +4403,8 @@ static void forward_chars_with_skipping (GtkTextIter *iter, gint count, gboolean skip_invisible, - gboolean skip_nontext) + gboolean skip_nontext, + gboolean skip_decomp) { gint i; @@ -4100,6 +4417,10 @@ forward_chars_with_skipping (GtkTextIter *iter, { gboolean ignored = FALSE; + /* minimal workaround to avoid the infinite loop of bug #168247. */ + if (gtk_text_iter_is_end (iter)) + return; + if (skip_nontext && gtk_text_iter_get_char (iter) == GTK_TEXT_UNKNOWN_CHAR) ignored = TRUE; @@ -4109,6 +4430,25 @@ forward_chars_with_skipping (GtkTextIter *iter, _gtk_text_btree_char_is_invisible (iter)) ignored = TRUE; + if (!ignored && skip_decomp) + { + /* being UTF8 correct sucks: this accounts for extra + offsets coming from canonical decompositions of + UTF8 characters (e.g. accented characters) which + g_utf8_normalize() performs */ + gchar *normal; + gchar *casefold; + gchar buffer[6]; + gint buffer_len; + + buffer_len = g_unichar_to_utf8 (gtk_text_iter_get_char (iter), buffer); + casefold = g_utf8_casefold (buffer, buffer_len); + normal = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); + i -= (g_utf8_strlen (normal, -1) - 1); + g_free (normal); + g_free (casefold); + } + gtk_text_iter_forward_char (iter); if (!ignored) @@ -4116,11 +4456,209 @@ forward_chars_with_skipping (GtkTextIter *iter, } } +static const gchar * +pointer_from_offset_skipping_decomp (const gchar *str, + gint offset) +{ + gchar *casefold, *normal; + const gchar *p, *q; + + p = str; + + while (offset > 0) + { + q = g_utf8_next_char (p); + casefold = g_utf8_casefold (p, q - p); + normal = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); + offset -= g_utf8_strlen (normal, -1); + g_free (casefold); + g_free (normal); + p = q; + } + + return p; +} + +static gboolean +exact_prefix_cmp (const gchar *string, + const gchar *prefix, + guint prefix_len) +{ + GUnicodeType type; + + if (strncmp (string, prefix, prefix_len) != 0) + return FALSE; + if (string[prefix_len] == '\0') + return TRUE; + + type = g_unichar_type (g_utf8_get_char (string + prefix_len)); + + /* If string contains prefix, check that prefix is not followed + * by a unicode mark symbol, e.g. that trailing 'a' in prefix + * is not part of two-char a-with-hat symbol in string. */ + return type != G_UNICODE_SPACING_MARK && + type != G_UNICODE_ENCLOSING_MARK && + type != G_UNICODE_NON_SPACING_MARK; +} + +static const gchar * +utf8_strcasestr (const gchar *haystack, + const gchar *needle) +{ + gsize needle_len; + gsize haystack_len; + const gchar *ret = NULL; + gchar *p; + gchar *casefold; + gchar *caseless_haystack; + gint i; + + g_return_val_if_fail (haystack != NULL, NULL); + g_return_val_if_fail (needle != NULL, NULL); + + casefold = g_utf8_casefold (haystack, -1); + caseless_haystack = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); + g_free (casefold); + + needle_len = g_utf8_strlen (needle, -1); + haystack_len = g_utf8_strlen (caseless_haystack, -1); + + if (needle_len == 0) + { + ret = (gchar *)haystack; + goto finally; + } + + if (haystack_len < needle_len) + { + ret = NULL; + goto finally; + } + + p = (gchar *)caseless_haystack; + needle_len = strlen (needle); + i = 0; + + while (*p) + { + if (exact_prefix_cmp (p, needle, needle_len)) + { + ret = pointer_from_offset_skipping_decomp (haystack, i); + goto finally; + } + + p = g_utf8_next_char (p); + i++; + } + +finally: + g_free (caseless_haystack); + + return ret; +} + +static const gchar * +utf8_strrcasestr (const gchar *haystack, + const gchar *needle) +{ + gsize needle_len; + gsize haystack_len; + const gchar *ret = NULL; + gchar *p; + gchar *casefold; + gchar *caseless_haystack; + gint i; + + g_return_val_if_fail (haystack != NULL, NULL); + g_return_val_if_fail (needle != NULL, NULL); + + casefold = g_utf8_casefold (haystack, -1); + caseless_haystack = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); + g_free (casefold); + + needle_len = g_utf8_strlen (needle, -1); + haystack_len = g_utf8_strlen (caseless_haystack, -1); + + if (needle_len == 0) + { + ret = (gchar *)haystack; + goto finally; + } + + if (haystack_len < needle_len) + { + ret = NULL; + goto finally; + } + + i = haystack_len - needle_len; + p = g_utf8_offset_to_pointer (caseless_haystack, i); + needle_len = strlen (needle); + + while (p >= caseless_haystack) + { + if (exact_prefix_cmp (p, needle, needle_len)) + { + ret = pointer_from_offset_skipping_decomp (haystack, i); + goto finally; + } + + p = g_utf8_prev_char (p); + i--; + } + +finally: + g_free (caseless_haystack); + + return ret; +} + +/* normalizes caseless strings and returns true if @s2 matches + the start of @s1 */ +static gboolean +utf8_caselessnmatch (const gchar *s1, + const gchar *s2, + gssize n1, + gssize n2) +{ + gchar *casefold; + gchar *normalized_s1; + gchar *normalized_s2; + gint len_s1; + gint len_s2; + gboolean ret = FALSE; + + g_return_val_if_fail (s1 != NULL, FALSE); + g_return_val_if_fail (s2 != NULL, FALSE); + g_return_val_if_fail (n1 > 0, FALSE); + g_return_val_if_fail (n2 > 0, FALSE); + + casefold = g_utf8_casefold (s1, n1); + normalized_s1 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); + g_free (casefold); + + casefold = g_utf8_casefold (s2, n2); + normalized_s2 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); + g_free (casefold); + + len_s1 = strlen (normalized_s1); + len_s2 = strlen (normalized_s2); + + if (len_s1 >= len_s2) + ret = (strncmp (normalized_s1, normalized_s2, len_s2) == 0); + + g_free (normalized_s1); + g_free (normalized_s2); + + return ret; +} + static gboolean lines_match (const GtkTextIter *start, const gchar **lines, gboolean visible_only, gboolean slice, + gboolean case_insensitive, GtkTextIter *match_start, GtkTextIter *match_end) { @@ -4164,14 +4702,25 @@ lines_match (const GtkTextIter *start, } if (match_start) /* if this is the first line we're matching */ - found = strstr (line_text, *lines); + { + if (!case_insensitive) + found = strstr (line_text, *lines); + else + found = utf8_strcasestr (line_text, *lines); + } else { /* If it's not the first line, we have to match from the * start of the line. */ - if (strncmp (line_text, *lines, strlen (*lines)) == 0) - found = line_text; + if ((!case_insensitive && + (strncmp (line_text, *lines, strlen (*lines)) == 0)) || + (case_insensitive && + utf8_caselessnmatch (line_text, *lines, strlen (line_text), + strlen (*lines)))) + { + found = line_text; + } else found = NULL; } @@ -4190,19 +4739,14 @@ lines_match (const GtkTextIter *start, /* If match start needs to be returned, set it to the * start of the search string. */ + forward_chars_with_skipping (&next, offset, + visible_only, !slice, FALSE); if (match_start) - { - *match_start = next; - - forward_chars_with_skipping (match_start, offset, - visible_only, !slice); - } + *match_start = next; /* Go to end of search string */ - offset += g_utf8_strlen (*lines, -1); - - forward_chars_with_skipping (&next, offset, - visible_only, !slice); + forward_chars_with_skipping (&next, g_utf8_strlen (*lines, -1), + visible_only, !slice, TRUE); g_free (line_text); @@ -4214,17 +4758,20 @@ lines_match (const GtkTextIter *start, /* pass NULL for match_start, since we don't need to find the * start again. */ - return lines_match (&next, lines, visible_only, slice, NULL, match_end); + return lines_match (&next, lines, visible_only, slice, case_insensitive, NULL, match_end); } /* strsplit () that retains the delimiter as part of the string. */ static gchar ** strbreakup (const char *string, const char *delimiter, - gint max_tokens) + gint max_tokens, + gint *num_strings, + gboolean case_insensitive) { GSList *string_list = NULL, *slist; gchar **str_array, *s; + gchar *casefold, *new_string; guint i, n = 1; g_return_val_if_fail (string != NULL, NULL); @@ -4241,12 +4788,20 @@ strbreakup (const char *string, do { guint len; - gchar *new_string; len = s - string + delimiter_len; new_string = g_new (gchar, len + 1); strncpy (new_string, string, len); new_string[len] = 0; + + if (case_insensitive) + { + casefold = g_utf8_casefold (new_string, -1); + g_free (new_string); + new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); + g_free (casefold); + } + string_list = g_slist_prepend (string_list, new_string); n++; string = s + delimiter_len; @@ -4257,7 +4812,17 @@ strbreakup (const char *string, if (*string) { n++; - string_list = g_slist_prepend (string_list, g_strdup (string)); + + if (case_insensitive) + { + casefold = g_utf8_casefold (string, -1); + new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); + g_free (casefold); + } + else + new_string = g_strdup (string); + + string_list = g_slist_prepend (string_list, new_string); } str_array = g_new (gchar*, n); @@ -4270,6 +4835,9 @@ strbreakup (const char *string, g_slist_free (string_list); + if (num_strings != NULL) + *num_strings = n - 1; + return str_array; } @@ -4278,12 +4846,13 @@ strbreakup (const char *string, * @iter: start of search * @str: a search string * @flags: flags affecting how the search is done - * @match_start: return location for start of match, or %NULL - * @match_end: return location for end of match, or %NULL - * @limit: bound for the search, or %NULL for the end of the buffer - * - * Searches forward for @str. Any match is returned as the range - * @match_start, @match_end. The search will not continue past + * @match_start: (out caller-allocates) (allow-none): return location for start of match, or %NULL + * @match_end: (out caller-allocates) (allow-none): return location for end of match, or %NULL + * @limit: (allow-none): bound for the search, or %NULL for the end of the buffer + * + * Searches forward for @str. Any match is returned by setting + * @match_start to the first character of the match and @match_end to the + * first character after the match. The search will not continue past * @limit. Note that a search is a linear or O(n) operation, so you * may wish to use @limit to avoid locking up your UI on large * buffers. @@ -4295,6 +4864,8 @@ strbreakup (const char *string, * pixbufs or child widgets mixed inside the matched range. If these * flags are not given, the match must be exact; the special 0xFFFC * character in @str will match embedded pixbufs or child widgets. + * If you specify the #GTK_TEXT_SEARCH_CASE_INSENSITIVE flag, the text will + * be matched regardless of what case it is in. * * Return value: whether a match was found **/ @@ -4312,7 +4883,8 @@ gtk_text_iter_forward_search (const GtkTextIter *iter, GtkTextIter search; gboolean visible_only; gboolean slice; - + gboolean case_insensitive; + g_return_val_if_fail (iter != NULL, FALSE); g_return_val_if_fail (str != NULL, FALSE); @@ -4343,10 +4915,11 @@ gtk_text_iter_forward_search (const GtkTextIter *iter, visible_only = (flags & GTK_TEXT_SEARCH_VISIBLE_ONLY) != 0; slice = (flags & GTK_TEXT_SEARCH_TEXT_ONLY) == 0; - + case_insensitive = (flags & GTK_TEXT_SEARCH_CASE_INSENSITIVE) != 0; + /* locate all lines */ - lines = strbreakup (str, "\n", -1); + lines = strbreakup (str, "\n", -1, NULL, case_insensitive); search = *iter; @@ -4363,11 +4936,11 @@ gtk_text_iter_forward_search (const GtkTextIter *iter, break; if (lines_match (&search, (const gchar**)lines, - visible_only, slice, &match, &end)) + visible_only, slice, case_insensitive, &match, &end)) { if (limit == NULL || (limit && - gtk_text_iter_compare (&end, limit) < 0)) + gtk_text_iter_compare (&end, limit) <= 0)) { retval = TRUE; @@ -4389,8 +4962,9 @@ gtk_text_iter_forward_search (const GtkTextIter *iter, } static gboolean -vectors_equal_ignoring_trailing (gchar **vec1, - gchar **vec2) +vectors_equal_ignoring_trailing (gchar **vec1, + gchar **vec2, + gboolean case_insensitive) { /* Ignores trailing chars in vec2's last line */ @@ -4401,37 +4975,67 @@ vectors_equal_ignoring_trailing (gchar **vec1, while (*i1 && *i2) { - if (strcmp (*i1, *i2) != 0) + gint len1; + gint len2; + + if (!case_insensitive) { - if (*(i2 + 1) == NULL) /* if this is the last line */ + if (strcmp (*i1, *i2) != 0) { - gint len1 = strlen (*i1); - gint len2 = strlen (*i2); - - if (len2 >= len1 && - strncmp (*i1, *i2, len1) == 0) + if (*(i2 + 1) == NULL) /* if this is the last line */ { - /* We matched ignoring the trailing stuff in vec2 */ - return TRUE; + len1 = strlen (*i1); + len2 = strlen (*i2); + + if (len2 >= len1 && + strncmp (*i1, *i2, len1) == 0) + { + /* We matched ignoring the trailing stuff in vec2 */ + return TRUE; + } + else + { + return FALSE; + } } else { return FALSE; } } - else + } + else + { + len1 = strlen (*i1); + len2 = strlen (*i2); + + if (!utf8_caselessnmatch (*i1, *i2, len1, len2)) { - return FALSE; + if (*(i2 + 1) == NULL) /* if this is the last line */ + { + if (utf8_caselessnmatch (*i2, *i1, len2, len1)) + { + /* We matched ignoring the trailing stuff in vec2 */ + return TRUE; + } + else + { + return FALSE; + } + } + else + { + return FALSE; + } } } + ++i1; ++i2; } if (*i1 || *i2) - { - return FALSE; - } + return FALSE; else return TRUE; } @@ -4442,10 +5046,12 @@ struct _LinesWindow { gint n_lines; gchar **lines; + GtkTextIter first_line_start; GtkTextIter first_line_end; - gboolean slice; - gboolean visible_only; + + guint slice : 1; + guint visible_only : 1; }; static void @@ -4506,6 +5112,8 @@ lines_window_init (LinesWindow *win, } win->lines[i] = line_text; + win->first_line_start = line_start; + win->first_line_end = line_end; line_end = line_start; gtk_text_iter_backward_line (&line_start); @@ -4569,54 +5177,17 @@ lines_window_free (LinesWindow *win) g_strfreev (win->lines); } -static gchar* -my_strrstr (const gchar *haystack, - const gchar *needle) -{ - /* FIXME GLib should have a nice implementation in it, this - * is slow-ass crap. - */ - - gint haystack_len = strlen (haystack); - gint needle_len = strlen (needle); - const gchar *needle_end = needle + needle_len; - const gchar *haystack_rend = haystack - 1; - const gchar *needle_rend = needle - 1; - const gchar *p; - - p = haystack + haystack_len; - while (p != haystack) - { - const gchar *n = needle_end - 1; - const gchar *s = p - 1; - while (s != haystack_rend && - n != needle_rend && - *s == *n) - { - --n; - --s; - } - - if (n == needle_rend) - return (gchar*)++s; - - --p; - } - - return NULL; -} - /** * gtk_text_iter_backward_search: * @iter: a #GtkTextIter where the search begins * @str: search string * @flags: bitmask of flags affecting the search - * @match_start: return location for start of match, or %NULL - * @match_end: return location for end of match, or %NULL - * @limit: location of last possible @match_start, or %NULL for start of buffer - * + * @match_start: (out caller-allocates) (allow-none): return location for start of match, or %NULL + * @match_end: (out caller-allocates) (allow-none): return location for end of match, or %NULL + * @limit: (allow-none): location of last possible @match_start, or %NULL for start of buffer + * * Same as gtk_text_iter_forward_search(), but moves backward. - * + * * Return value: whether a match was found **/ gboolean @@ -4634,6 +5205,7 @@ gtk_text_iter_backward_search (const GtkTextIter *iter, gboolean retval = FALSE; gboolean visible_only; gboolean slice; + gboolean case_insensitive; g_return_val_if_fail (iter != NULL, FALSE); g_return_val_if_fail (str != NULL, FALSE); @@ -4664,18 +5236,11 @@ gtk_text_iter_backward_search (const GtkTextIter *iter, visible_only = (flags & GTK_TEXT_SEARCH_VISIBLE_ONLY) != 0; slice = (flags & GTK_TEXT_SEARCH_TEXT_ONLY) == 0; - - /* locate all lines */ + case_insensitive = (flags & GTK_TEXT_SEARCH_CASE_INSENSITIVE) != 0; - lines = strbreakup (str, "\n", -1); + /* locate all lines */ - l = lines; - n_lines = 0; - while (*l) - { - ++n_lines; - ++l; - } + lines = strbreakup (str, "\n", -1, &n_lines, case_insensitive); win.n_lines = n_lines; win.slice = slice; @@ -4688,7 +5253,7 @@ gtk_text_iter_backward_search (const GtkTextIter *iter, do { - gchar *first_line_match; + const gchar *first_line_match; if (limit && gtk_text_iter_compare (limit, &win.first_line_end) > 0) @@ -4701,10 +5266,14 @@ gtk_text_iter_backward_search (const GtkTextIter *iter, * end in '\n', so this will only match at the * end of the first line, which is correct. */ - first_line_match = my_strrstr (*win.lines, *lines); + if (!case_insensitive) + first_line_match = g_strrstr (*win.lines, *lines); + else + first_line_match = utf8_strrcasestr (*win.lines, *lines); if (first_line_match && - vectors_equal_ignoring_trailing (lines + 1, win.lines + 1)) + vectors_equal_ignoring_trailing (lines + 1, win.lines + 1, + case_insensitive)) { /* Match! */ gint offset; @@ -4717,7 +5286,7 @@ gtk_text_iter_backward_search (const GtkTextIter *iter, next = win.first_line_start; start_tmp = next; forward_chars_with_skipping (&start_tmp, offset, - visible_only, !slice); + visible_only, !slice, FALSE); if (limit && gtk_text_iter_compare (limit, &start_tmp) > 0) @@ -4735,7 +5304,7 @@ gtk_text_iter_backward_search (const GtkTextIter *iter, } forward_chars_with_skipping (&next, offset, - visible_only, !slice); + visible_only, !slice, TRUE); if (match_end) *match_end = next; @@ -5034,7 +5603,10 @@ _gtk_text_btree_get_iter_at_first_toggle (GtkTextBTree *tree, else { iter_init_from_byte_offset (iter, tree, line, 0); - gtk_text_iter_forward_to_tag_toggle (iter, tag); + + if (!gtk_text_iter_toggles_tag (iter, tag)) + gtk_text_iter_forward_to_tag_toggle (iter, tag); + check_invariants (iter); return TRUE; } @@ -5106,8 +5678,10 @@ _gtk_text_btree_get_iter_at_child_anchor (GtkTextBTree *tree, g_return_if_fail (iter != NULL); g_return_if_fail (tree != NULL); g_return_if_fail (GTK_IS_TEXT_CHILD_ANCHOR (anchor)); + + seg = anchor->segment; - seg = anchor->segment; + g_assert (seg->body.child.line != NULL); iter_init_from_segment (iter, tree, seg->body.child.line, seg); @@ -5310,4 +5884,3 @@ _gtk_text_iter_check (const GtkTextIter *iter) if (_gtk_text_line_is_last (real->line, real->tree)) g_error ("Iterator was on last line (past the end iterator)"); } -