Impressum gtktextiter.c
Sprache: C
/* GTK - The GIMP Toolkit
* gtktextiter . c Copyright ( C ) 2000 Red Hat , Inc .
*
* This library is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ; either
* version 2 of the License , or ( at your option ) any later version .
*
* This library 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
* 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 , see < http : //www.gnu.org/licenses/>.
*/
/*
* Modified by the GTK + Team and others 1997 - 2000 . See the AUTHORS
* file for a list of people on the GTK + Team . See the ChangeLog
* files for a list of changes . These files are distributed with
* GTK + at ftp : //ftp.gtk.org/pub/gtk/.
*/
#include "config.h"
#include "gtktextiter.h"
#include "gtktextbtreeprivate.h"
#include "gtktextbufferprivate.h"
#include "gtktextiterprivate.h"
#include "gtkdebug.h"
#include <string.h>
/**
* GtkTextIter :
*
* Iterates over the contents of a ` GtkTextBuffer ` .
*
* You may wish to begin by reading the
* [ text widget conceptual overview ] ( section - text - widget . html ) ,
* 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 G_GNUC_MAY_ALIAS _GtkTextRealIter
{
/* Always-valid information */
GtkTextBTree *tree;
GtkTextLine *line;
/* At least one of these is always valid;
if invalid , they are - 1 .
If the line byte offset is valid , so is the segment byte offset ;
and ditto for char offsets. */
int line_byte_offset;
int line_char_offset;
/* These two are valid if >= 0 */
int cached_char_index;
int cached_line_number;
/* Stamps to detect the buffer changing under us */
int chars_changed_stamp;
int segments_changed_stamp;
/* Valid if the segments_changed_stamp is up-to-date */
GtkTextLineSegment *segment; /* indexable segment we index */
GtkTextLineSegment *any_segment; /* first segment in our location,
maybe same as "segment" */
/* One of these will always be valid if segments_changed_stamp is
up - to - date . If invalid , they are - 1 .
If the line byte offset is valid , so is the segment byte offset ;
and ditto for char offsets. */
int segment_byte_offset;
int segment_char_offset;
/* padding */
int pad1;
gpointer pad2;
};
/* These "set" functions should not assume any fields
other than the char stamp and the tree are valid .
*/
static void
iter_set_common (GtkTextRealIter *iter,
GtkTextLine *line)
{
/* Update segments stamp */
iter->segments_changed_stamp =
_gtk_text_btree_get_segments_changed_stamp (iter->tree);
iter->line = line;
iter->line_byte_offset = -1 ;
iter->line_char_offset = -1 ;
iter->segment_byte_offset = -1 ;
iter->segment_char_offset = -1 ;
iter->cached_char_index = -1 ;
iter->cached_line_number = -1 ;
}
static void
iter_set_from_byte_offset (GtkTextRealIter *iter,
GtkTextLine *line,
int byte_offset)
{
iter_set_common (iter, line);
if (!_gtk_text_line_byte_locate (iter->line,
byte_offset,
&iter->segment,
&iter->any_segment,
&iter->segment_byte_offset,
&iter->line_byte_offset))
g_error ("Byte index %d is off the end of the line" ,
byte_offset);
}
static void
iter_set_from_char_offset (GtkTextRealIter *iter,
GtkTextLine *line,
int char_offset)
{
iter_set_common (iter, line);
if (!_gtk_text_line_char_locate (iter->line,
char_offset,
&iter->segment,
&iter->any_segment,
&iter->segment_char_offset,
&iter->line_char_offset))
g_error ("Char offset %d is off the end of the line" ,
char_offset);
}
static void
iter_set_from_segment (GtkTextRealIter *iter,
GtkTextLine *line,
GtkTextLineSegment *segment)
{
GtkTextLineSegment *seg;
int byte_offset;
/* This could theoretically be optimized by computing all the iter
fields in this same loop, but I'm skipping it for now. */
byte_offset = 0 ;
seg = line->segments;
while (seg != segment)
{
byte_offset += seg->byte_count;
seg = seg->next;
}
iter_set_from_byte_offset (iter, line, byte_offset);
}
/* This function ensures that the segment-dependent information is
truly computed lazily ; often we don ' t need to do the full make_real
work . This ensures the btree and line are valid , but doesn ' t
update the segments. */
static GtkTextRealIter*
gtk_text_iter_make_surreal (const GtkTextIter *_iter)
{
GtkTextRealIter *iter = (GtkTextRealIter*)_iter;
if (iter->chars_changed_stamp !=
_gtk_text_btree_get_chars_changed_stamp (iter->tree))
{
g_warning ("Invalid text buffer iterator: either the iterator "
"is uninitialized, or the characters/paintables/widgets "
"in the buffer have been modified since the iterator "
"was created.\nYou must use marks, character numbers, "
"or line numbers to preserve a position across buffer "
"modifications.\nYou can apply tags and insert marks "
"without invalidating your iterators,\n"
"but any mutation that affects 'indexable' buffer contents "
"(contents that can be referred to by character offset)\n"
"will invalidate all outstanding iterators" );
return NULL;
}
/* We don't update the segments information since we are becoming
only surreal . However we do invalidate the segments information
if appropriate , to be sure we segfault if we try to use it and we
should have used make_real. */
if (iter->segments_changed_stamp !=
_gtk_text_btree_get_segments_changed_stamp (iter->tree))
{
iter->segment = NULL;
iter->any_segment = NULL;
/* set to segfault-causing values. */
iter->segment_byte_offset = -10000 ;
iter->segment_char_offset = -10000 ;
}
return iter;
}
static GtkTextRealIter*
gtk_text_iter_make_real (const GtkTextIter *_iter)
{
GtkTextRealIter *iter;
iter = gtk_text_iter_make_surreal (_iter);
if (iter == NULL)
return NULL;
if (iter->segments_changed_stamp !=
_gtk_text_btree_get_segments_changed_stamp (iter->tree))
{
if (iter->line_byte_offset >= 0 )
{
iter_set_from_byte_offset (iter,
iter->line,
iter->line_byte_offset);
}
else
{
g_assert (iter->line_char_offset >= 0 );
iter_set_from_char_offset (iter,
iter->line,
iter->line_char_offset);
}
}
g_assert (iter->segment != NULL);
g_assert (iter->any_segment != NULL);
g_assert (iter->segment->char_count > 0 );
return iter;
}
static GtkTextRealIter*
iter_init_common (GtkTextIter *_iter,
GtkTextBTree *tree)
{
GtkTextRealIter *iter = (GtkTextRealIter*)_iter;
g_assert (iter != NULL);
g_assert (tree != NULL);
memset (iter, 0 , sizeof (GtkTextRealIter));
iter->tree = tree;
iter->chars_changed_stamp =
_gtk_text_btree_get_chars_changed_stamp (iter->tree);
return iter;
}
static GtkTextRealIter*
iter_init_from_segment (GtkTextIter *iter,
GtkTextBTree *tree,
GtkTextLine *line,
GtkTextLineSegment *segment)
{
GtkTextRealIter *real;
g_return_val_if_fail (line != NULL, NULL);
real = iter_init_common (iter, tree);
iter_set_from_segment (real, line, segment);
return real;
}
static GtkTextRealIter*
iter_init_from_byte_offset (GtkTextIter *iter,
GtkTextBTree *tree,
GtkTextLine *line,
int line_byte_offset)
{
GtkTextRealIter *real;
g_return_val_if_fail (line != NULL, NULL);
real = iter_init_common (iter, tree);
iter_set_from_byte_offset (real, line, line_byte_offset);
if (real->segment->type == >k_text_char_type &&
(real->segment->body.chars[real->segment_byte_offset] & 0 xc0) == 0 x80)
g_warning ("Incorrect line 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." ,
line_byte_offset);
return real;
}
static GtkTextRealIter*
iter_init_from_char_offset (GtkTextIter *iter,
GtkTextBTree *tree,
GtkTextLine *line,
int line_char_offset)
{
GtkTextRealIter *real;
g_return_val_if_fail (line != NULL, NULL);
real = iter_init_common (iter, tree);
iter_set_from_char_offset (real, line, line_char_offset);
return real;
}
static inline void
invalidate_char_index (GtkTextRealIter *iter)
{
iter->cached_char_index = -1 ;
}
static inline void
adjust_char_index (GtkTextRealIter *iter, int count)
{
if (iter->cached_char_index >= 0 )
iter->cached_char_index += count;
}
static inline void
adjust_line_number (GtkTextRealIter *iter, int count)
{
if (iter->cached_line_number >= 0 )
iter->cached_line_number += count;
}
static inline void
ensure_char_offsets (GtkTextRealIter *iter)
{
if (iter->line_char_offset < 0 )
{
g_assert (iter->line_byte_offset >= 0 );
_gtk_text_line_byte_to_char_offsets (iter->line,
iter->line_byte_offset,
&iter->line_char_offset,
&iter->segment_char_offset);
}
}
static inline void
ensure_byte_offsets (GtkTextRealIter *iter)
{
if (iter->line_byte_offset < 0 )
{
g_assert (iter->line_char_offset >= 0 );
_gtk_text_line_char_to_byte_offsets (iter->line,
iter->line_char_offset,
&iter->line_byte_offset,
&iter->segment_byte_offset);
}
}
static inline gboolean
is_segment_start (GtkTextRealIter *real)
{
return real->segment_byte_offset == 0 || real->segment_char_offset == 0 ;
}
static void
check_invariants (const GtkTextIter *iter)
{
if (GTK_DEBUG_CHECK (TEXT))
_gtk_text_iter_check (iter);
}
/**
* gtk_text_iter_get_buffer :
* @ iter : an iterator
*
* Returns the ` GtkTextBuffer ` this iterator is associated with .
*
* Returns : ( transfer none ) : the buffer
**/
GtkTextBuffer*
gtk_text_iter_get_buffer (const GtkTextIter *iter)
{
GtkTextRealIter *real;
g_return_val_if_fail (iter != NULL, NULL);
real = gtk_text_iter_make_surreal (iter);
if (real == NULL)
return NULL;
check_invariants (iter);
return _gtk_text_btree_get_buffer (real->tree);
}
/**
* gtk_text_iter_copy :
* @ iter : an iterator
*
* 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 .
*
* Returns : a copy of the @ iter , free with [ method @ Gtk . TextIter . free ]
*/
GtkTextIter*
gtk_text_iter_copy (const GtkTextIter *iter)
{
GtkTextIter *new_iter;
g_return_val_if_fail (iter != NULL, NULL);
new_iter = g_new (GtkTextIter, 1 );
*new_iter = *iter;
return new_iter;
}
/**
* gtk_text_iter_free :
* @ iter : a dynamically - allocated iterator
*
* Free an iterator allocated on the heap .
*
* This function 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);
}
/**
* 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 .
*/
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, NULL);
real = gtk_text_iter_make_real (iter);
if (real == NULL)
return NULL;
check_invariants (iter);
g_assert (real->segment != NULL);
return real->segment;
}
GtkTextLineSegment*
_gtk_text_iter_get_any_segment (const GtkTextIter *iter)
{
GtkTextRealIter *real;
g_return_val_if_fail (iter != NULL, NULL);
real = gtk_text_iter_make_real (iter);
if (real == NULL)
return NULL;
check_invariants (iter);
g_assert (real->any_segment != NULL);
return real->any_segment;
}
int
_gtk_text_iter_get_segment_byte (const GtkTextIter *iter)
{
GtkTextRealIter *real;
g_return_val_if_fail (iter != NULL, 0 );
real = gtk_text_iter_make_real (iter);
if (real == NULL)
return 0 ;
ensure_byte_offsets (real);
check_invariants (iter);
return real->segment_byte_offset;
}
int
_gtk_text_iter_get_segment_char (const GtkTextIter *iter)
{
GtkTextRealIter *real;
g_return_val_if_fail (iter != NULL, 0 );
real = gtk_text_iter_make_real (iter);
if (real == NULL)
return 0 ;
ensure_char_offsets (real);
check_invariants (iter);
return real->segment_char_offset;
}
/* This function does not require a still-valid
iterator */
GtkTextLine*
_gtk_text_iter_get_text_line (const GtkTextIter *iter)
{
const GtkTextRealIter *real;
g_return_val_if_fail (iter != NULL, NULL);
real = (const GtkTextRealIter*)iter;
return real->line;
}
/* This function does not require a still-valid
iterator */
GtkTextBTree*
_gtk_text_iter_get_btree (const GtkTextIter *iter)
{
const GtkTextRealIter *real;
g_return_val_if_fail (iter != NULL, NULL);
real = (const GtkTextRealIter*)iter;
return real->tree;
}
/*
* Conversions
*/
/**
* gtk_text_iter_get_offset :
* @ iter : an iterator
*
* Returns the character offset of an iterator .
*
* Each character in a ` GtkTextBuffer ` has an offset ,
* starting with 0 for the first character in the buffer .
* Use [ method @ Gtk . TextBuffer . get_iter_at_offset ] to convert
* an offset back into an iterator .
*
* Returns : a character offset
*/
int
gtk_text_iter_get_offset (const GtkTextIter *iter)
{
GtkTextRealIter *real;
g_return_val_if_fail (iter != NULL, 0 );
real = gtk_text_iter_make_surreal (iter);
if (real == NULL)
return 0 ;
check_invariants (iter);
if (real->cached_char_index < 0 )
{
ensure_char_offsets (real);
real->cached_char_index =
_gtk_text_line_char_index (real->line);
real->cached_char_index += real->line_char_offset;
}
check_invariants (iter);
return real->cached_char_index;
}
/**
* gtk_text_iter_get_line :
* @ iter : an iterator
*
* Returns the line number containing the iterator .
*
* Lines in a ` GtkTextBuffer ` are numbered beginning
* with 0 for the first line in the buffer .
*
* Returns : a line number
*/
int
gtk_text_iter_get_line (const GtkTextIter *iter)
{
GtkTextRealIter *real;
g_return_val_if_fail (iter != NULL, 0 );
real = gtk_text_iter_make_surreal (iter);
if (real == NULL)
return 0 ;
if (real->cached_line_number < 0 )
real->cached_line_number =
_gtk_text_line_get_number (real->line);
check_invariants (iter);
return real->cached_line_number;
}
/**
* gtk_text_iter_get_line_offset :
* @ iter : an iterator
*
* Returns the character offset of the iterator ,
* counting from the start of a newline - terminated line .
*
* The first character on the line has offset 0 .
*
* Returns : offset from start of line
*/
int
gtk_text_iter_get_line_offset (const GtkTextIter *iter)
{
GtkTextRealIter *real;
g_return_val_if_fail (iter != NULL, 0 );
real = gtk_text_iter_make_surreal (iter);
if (real == NULL)
return 0 ;
ensure_char_offsets (real);
check_invariants (iter);
return real->line_char_offset;
}
/**
* gtk_text_iter_get_line_index :
* @ iter : an iterator
*
* Returns the byte index of the iterator , counting
* from the start of a newline - terminated line .
*
* Remember that ` GtkTextBuffer ` encodes text in
* UTF - 8 , and that characters can require a variable
* number of bytes to represent .
*
* Returns : distance from start of line , in bytes
*/
int
gtk_text_iter_get_line_index (const GtkTextIter *iter)
{
GtkTextRealIter *real;
g_return_val_if_fail (iter != NULL, 0 );
real = gtk_text_iter_make_surreal (iter);
if (real == NULL)
return 0 ;
ensure_byte_offsets (real);
check_invariants (iter);
return real->line_byte_offset;
}
/**
* gtk_text_iter_get_visible_line_offset :
* @ iter : a ` GtkTextIter `
*
* Returns the offset in characters from the start of the
* line to the given @ iter , not counting characters that
* are invisible due to tags with the “ invisible ” flag
* toggled on .
*
* Returns : offset in visible characters from the start of the line
*/
int
gtk_text_iter_get_visible_line_offset (const GtkTextIter *iter)
{
GtkTextRealIter *real;
int vis_offset;
GtkTextLineSegment *seg;
GtkTextIter pos;
g_return_val_if_fail (iter != NULL, 0 );
real = gtk_text_iter_make_real (iter);
if (real == NULL)
return 0 ;
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,
0 );
seg = _gtk_text_iter_get_indexable_segment (&pos);
while (seg != real->segment)
{
/* This is a pretty expensive call, making the
* whole function pretty lame ; we could keep track
* of current invisibility state by looking at toggle
* segments as we loop , and then call this function
* only once per line , in order to speed up the loop
* quite a lot .
*/
if (_gtk_text_btree_char_is_invisible (&pos))
vis_offset -= seg->char_count;
_gtk_text_iter_forward_indexable_segment (&pos);
seg = _gtk_text_iter_get_indexable_segment (&pos);
}
if (_gtk_text_btree_char_is_invisible (&pos))
vis_offset -= real->segment_char_offset;
return vis_offset;
}
/**
* gtk_text_iter_get_visible_line_index :
* @ iter : a ` GtkTextIter `
*
* Returns the number of bytes from the start of the
* line to the given @ iter , not counting bytes that
* are invisible due to tags with the “ invisible ” flag
* toggled on .
*
* Returns : byte index of @ iter with respect to the start of the line
*/
int
gtk_text_iter_get_visible_line_index (const GtkTextIter *iter)
{
GtkTextRealIter *real;
int vis_offset;
GtkTextLineSegment *seg;
GtkTextIter pos;
g_return_val_if_fail (iter != NULL, 0 );
real = gtk_text_iter_make_real (iter);
if (real == NULL)
return 0 ;
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,
0 );
seg = _gtk_text_iter_get_indexable_segment (&pos);
while (seg != real->segment)
{
/* This is a pretty expensive call, making the
* whole function pretty lame ; we could keep track
* of current invisibility state by looking at toggle
* segments as we loop , and then call this function
* only once per line , in order to speed up the loop
* quite a lot .
*/
if (_gtk_text_btree_char_is_invisible (&pos))
vis_offset -= seg->byte_count;
_gtk_text_iter_forward_indexable_segment (&pos);
seg = _gtk_text_iter_get_indexable_segment (&pos);
}
if (_gtk_text_btree_char_is_invisible (&pos))
vis_offset -= real->segment_byte_offset;
return vis_offset;
}
/*
* Dereferencing
*/
/**
* gtk_text_iter_get_char :
* @ iter : an iterator
*
* The Unicode character at this iterator is returned .
*
* Equivalent to 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 0 xFFFC
* is returned . If invoked on the end iterator , zero is returned ;
* zero is not a valid Unicode character .
*
* So you can write a loop which ends when this function returns 0 .
*
* Returns : a Unicode character , or 0 if @ iter is not dereferenceable
*/
gunichar
gtk_text_iter_get_char (const GtkTextIter *iter)
{
GtkTextRealIter *real;
g_return_val_if_fail (iter != NULL, 0 );
real = gtk_text_iter_make_real (iter);
if (real == NULL)
return 0 ;
check_invariants (iter);
if (gtk_text_iter_is_end (iter))
return 0 ;
else if (real->segment->type == >k_text_char_type)
{
ensure_byte_offsets (real);
return g_utf8_get_char (real->segment->body.chars +
real->segment_byte_offset);
}
else if (real->segment->type == >k_text_child_type)
{
return g_utf8_get_char (gtk_text_child_anchor_get_replacement (real->segment->body.child.obj));
}
else
{
/* Unicode "unknown character" 0xFFFC */
return GTK_TEXT_UNKNOWN_CHAR;
}
}
/**
* gtk_text_iter_get_slice :
* @ start : iterator at start of a range
* @ end : iterator at end of a range
*
* Returns the text in the given range .
*
* A “ slice ” is an array of characters encoded in UTF - 8 format ,
* including the Unicode “ unknown ” character 0 xFFFC for iterable
* non - character elements in the buffer , such as images .
* Because images are encoded in the slice , byte and
* character offsets in the returned array will correspond to byte
* offsets in the text buffer . Note that 0 xFFFC can occur in normal
* text as well , so it is not a reliable indicator that a paintable or
* widget is in the buffer .
*
* Returns : ( transfer full ) : slice of text from the buffer
*/
char *
gtk_text_iter_get_slice (const GtkTextIter *start,
const GtkTextIter *end)
{
g_return_val_if_fail (start != NULL, NULL);
g_return_val_if_fail (end != NULL, NULL);
check_invariants (start);
check_invariants (end);
return _gtk_text_btree_get_text (start, end, TRUE , TRUE );
}
/**
* gtk_text_iter_get_text :
* @ start : iterator at start of a range
* @ end : iterator at end of a range
*
* Returns text in the given range .
*
* If the range
* contains non - text elements such as images , the character and byte
* offsets in the returned string will not correspond to character and
* byte offsets in the buffer . If you want offsets to correspond , see
* [ method @ Gtk . TextIter . get_slice ] .
*
* Returns : ( transfer full ) : array of characters from the buffer
*/
char *
gtk_text_iter_get_text (const GtkTextIter *start,
const GtkTextIter *end)
{
g_return_val_if_fail (start != NULL, NULL);
g_return_val_if_fail (end != NULL, NULL);
check_invariants (start);
check_invariants (end);
return _gtk_text_btree_get_text (start, end, TRUE , FALSE );
}
/**
* gtk_text_iter_get_visible_slice :
* @ start : iterator at start of range
* @ end : iterator at end of range
*
* Returns visible text in the given range .
*
* Like [ method @ Gtk . TextIter . get_slice ] , but invisible text
* is not included . Invisible text is usually invisible because
* a ` GtkTextTag ` with the “ invisible ” attribute turned on has
* been applied to it .
*
* Returns : ( transfer full ) : slice of text from the buffer
*/
char *
gtk_text_iter_get_visible_slice (const GtkTextIter *start,
const GtkTextIter *end)
{
g_return_val_if_fail (start != NULL, NULL);
g_return_val_if_fail (end != NULL, NULL);
check_invariants (start);
check_invariants (end);
return _gtk_text_btree_get_text (start, end, FALSE , TRUE );
}
/**
* gtk_text_iter_get_visible_text :
* @ start : iterator at start of range
* @ end : iterator at end of range
*
* Returns visible text in the given range .
*
* Like [ method @ Gtk . TextIter . get_text ] , but invisible text
* is not included . Invisible text is usually invisible because
* a ` GtkTextTag ` with the “ invisible ” attribute turned on has
* been applied to it .
*
* Returns : ( transfer full ) : string containing visible text in the
* range
*/
char *
gtk_text_iter_get_visible_text (const GtkTextIter *start,
const GtkTextIter *end)
{
g_return_val_if_fail (start != NULL, NULL);
g_return_val_if_fail (end != NULL, NULL);
check_invariants (start);
check_invariants (end);
return _gtk_text_btree_get_text (start, end, FALSE , FALSE );
}
/**
* gtk_text_iter_get_paintable :
* @ iter : an iterator
*
* If the element at @ iter is a paintable , the paintable is returned .
*
* Otherwise , % NULL is returned .
*
* Returns : ( transfer none ) ( nullable ) : the paintable at @ iter
**/
GdkPaintable *
gtk_text_iter_get_paintable (const GtkTextIter *iter)
{
GtkTextRealIter *real;
g_return_val_if_fail (iter != NULL, NULL);
real = gtk_text_iter_make_real (iter);
if (real == NULL)
return NULL;
check_invariants (iter);
if (real->segment->type != >k_text_paintable_type)
return NULL;
else
return real->segment->body.paintable.paintable;
}
/**
* gtk_text_iter_get_child_anchor :
* @ iter : an iterator
*
* If the location at @ iter contains a child anchor , the
* anchor is returned .
*
* Otherwise , % NULL is returned .
*
* Returns : ( transfer none ) ( nullable ) : the anchor at @ iter
**/
GtkTextChildAnchor*
gtk_text_iter_get_child_anchor (const GtkTextIter *iter)
{
GtkTextRealIter *real;
g_return_val_if_fail (iter != NULL, NULL);
real = gtk_text_iter_make_real (iter);
if (real == NULL)
return NULL;
check_invariants (iter);
if (real->segment->type != >k_text_child_type)
return NULL;
else
return real->segment->body.child.obj;
}
/**
* gtk_text_iter_get_marks :
* @ iter : an iterator
*
* Returns a list of all ` GtkTextMark ` at this location .
*
* Because marks are not iterable ( they don ’ t take up any " space "
* in the buffer , they are just marks in between iterable locations ) ,
* multiple marks can exist in the same place .
*
* The returned list is not in any meaningful order .
*
* Returns : ( element - type GtkTextMark ) ( transfer container ) :
* list of ` GtkTextMark `
*/
GSList*
gtk_text_iter_get_marks (const GtkTextIter *iter)
{
GtkTextRealIter *real;
GtkTextLineSegment *seg;
GSList *retval;
g_return_val_if_fail (iter != NULL, NULL);
real = gtk_text_iter_make_real (iter);
if (real == NULL)
return NULL;
check_invariants (iter);
retval = NULL;
seg = real->any_segment;
while (seg != real->segment)
{
if (seg->type == >k_text_left_mark_type ||
seg->type == >k_text_right_mark_type)
retval = g_slist_prepend (retval, seg->body.mark.obj);
seg = seg->next;
}
/* The returned list isn't guaranteed to be in any special order,
and it isn't. */
return retval;
}
/**
* gtk_text_iter_get_toggled_tags :
* @ iter : an iterator
* @ 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
* 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 .
*
* Returns : ( element - type GtkTextTag ) ( transfer container ) : tags
* toggled at this point
*/
GSList*
gtk_text_iter_get_toggled_tags (const GtkTextIter *iter,
gboolean toggled_on)
{
GtkTextRealIter *real;
GtkTextLineSegment *seg;
GSList *retval;
g_return_val_if_fail (iter != NULL, NULL);
real = gtk_text_iter_make_real (iter);
if (real == NULL)
return NULL;
check_invariants (iter);
retval = NULL;
seg = real->any_segment;
while (seg != real->segment)
{
if (toggled_on)
{
if (seg->type == >k_text_toggle_on_type)
{
retval = g_slist_prepend (retval, seg->body.toggle.info->tag);
}
}
else
{
if (seg->type == >k_text_toggle_off_type)
{
retval = g_slist_prepend (retval, seg->body.toggle.info->tag);
}
}
seg = seg->next;
}
/* The returned list isn't guaranteed to be in any special order,
and it isn't. */
return retval;
}
/**
* gtk_text_iter_starts_tag :
* @ iter : an iterator
* @ tag : ( nullable ) : a ` GtkTextTag `
*
* 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 if this function returns % TRUE , it means that
* @ iter is at the beginning of the tagged range , and that the
* character at @ iter is inside the tagged range . In other
* words , unlike [ method @ Gtk . TextIter . ends_tag ] , if
* this function returns % TRUE , [ method @ Gtk . TextIter . has_tag ]
* will also return % TRUE for the same parameters .
*
* Returns : whether @ iter is the start of a range tagged with @ tag
**/
gboolean
gtk_text_iter_starts_tag (const GtkTextIter *iter,
GtkTextTag *tag)
{
GtkTextRealIter *real;
GtkTextLineSegment *seg;
g_return_val_if_fail (iter != NULL, FALSE );
real = gtk_text_iter_make_real (iter);
if (real == NULL)
return FALSE ;
check_invariants (iter);
seg = real->any_segment;
while (seg != real->segment)
{
if (seg->type == >k_text_toggle_on_type)
{
if (tag == NULL ||
seg->body.toggle.info->tag == tag)
return TRUE ;
}
seg = seg->next;
}
return FALSE ;
}
/**
* gtk_text_iter_ends_tag :
* @ iter : an iterator
* @ tag : ( nullable ) : a ` GtkTextTag `
*
* 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 if this function returns % TRUE , it means that
* @ iter is at the end of the tagged range , but that the character
* at @ iter is outside the tagged range . In other words ,
* unlike [ method @ Gtk . TextIter . starts_tag ] , if this function
* returns % TRUE , [ method @ Gtk . TextIter . has_tag ] will return
* % FALSE for the same parameters .
*
* Returns : whether @ iter is the end of a range tagged with @ tag
*/
gboolean
gtk_text_iter_ends_tag (const GtkTextIter *iter,
GtkTextTag *tag)
{
GtkTextRealIter *real;
GtkTextLineSegment *seg;
g_return_val_if_fail (iter != NULL, FALSE );
real = gtk_text_iter_make_real (iter);
if (real == NULL)
return FALSE ;
check_invariants (iter);
seg = real->any_segment;
while (seg != real->segment)
{
if (seg->type == >k_text_toggle_off_type)
{
if (tag == NULL ||
seg->body.toggle.info->tag == tag)
return TRUE ;
}
seg = seg->next;
}
return FALSE ;
}
/**
* gtk_text_iter_toggles_tag :
* @ iter : an iterator
* @ tag : ( nullable ) : a ` GtkTextTag `
*
* Gets whether a range with @ tag applied to it begins
* or ends at @ iter .
*
* This is equivalent to ( gtk_text_iter_starts_tag ( ) | |
* gtk_text_iter_ends_tag ( ) )
*
* Returns : whether @ tag is toggled on or off at @ iter
*/
gboolean
gtk_text_iter_toggles_tag (const GtkTextIter *iter,
GtkTextTag *tag)
{
GtkTextRealIter *real;
GtkTextLineSegment *seg;
g_return_val_if_fail (iter != NULL, FALSE );
real = gtk_text_iter_make_real (iter);
if (real == NULL)
return FALSE ;
check_invariants (iter);
seg = real->any_segment;
while (seg != real->segment)
{
if ( (seg->type == >k_text_toggle_off_type ||
seg->type == >k_text_toggle_on_type) &&
(tag == NULL ||
seg->body.toggle.info->tag == tag) )
return TRUE ;
seg = seg->next;
}
return FALSE ;
}
/**
* gtk_text_iter_has_tag :
* @ iter : an iterator
* @ tag : a ` GtkTextTag `
*
* Returns % TRUE if @ iter points to a character that is part
* of a range tagged with @ tag .
*
* See also [ method @ Gtk . TextIter . starts_tag ] and
* [ method @ Gtk . TextIter . ends_tag ] .
*
* Returns : whether @ iter is tagged with @ tag
*/
gboolean
gtk_text_iter_has_tag (const GtkTextIter *iter,
GtkTextTag *tag)
{
GtkTextRealIter *real;
g_return_val_if_fail (iter != NULL, FALSE );
g_return_val_if_fail (GTK_IS_TEXT_TAG (tag), FALSE );
real = gtk_text_iter_make_surreal (iter);
if (real == NULL)
return FALSE ;
check_invariants (iter);
if (real->line_byte_offset >= 0 )
{
return _gtk_text_line_byte_has_tag (real->line, real->tree,
real->line_byte_offset, tag);
}
else
{
g_assert (real->line_char_offset >= 0 );
return _gtk_text_line_char_has_tag (real->line, real->tree,
real->line_char_offset, tag);
}
}
/**
* gtk_text_iter_get_tags :
* @ iter : a ` GtkTextIter `
*
* Returns a list of tags that apply to @ iter , in ascending order of
* priority .
*
* The highest - priority tags are last .
*
* The ` GtkTextTag ` s in the list don ’ t have a reference added ,
* but you have to free the list itself .
*
* Returns : ( element - type GtkTextTag ) ( transfer container ) : list of
* ` GtkTextTag `
*/
GSList*
gtk_text_iter_get_tags (const GtkTextIter *iter)
{
GPtrArray *tags;
GSList *retval;
g_return_val_if_fail (iter != NULL, NULL);
/* Get the tags at this spot */
tags = _gtk_text_btree_get_tags (iter);
/* No tags, use default style */
if (tags == NULL || tags->len == 0 )
{
if (tags)
g_ptr_array_unref (tags);
return NULL;
}
retval = NULL;
for (int i = tags->len - 1 ; i >= 0 ; i--)
retval = g_slist_prepend (retval, g_ptr_array_index (tags, i));
g_ptr_array_unref (tags);
return retval;
}
/**
* gtk_text_iter_editable :
* @ iter : an iterator
* @ default_setting : % TRUE if text is editable by default
*
* Returns whether the character at @ iter is within an editable region
* of text .
*
* Non - editable text is “ locked ” and can ’ t be changed by the
* user via ` GtkTextView ` . If no tags applied to this text affect
* editability , @ default_setting will be returned .
*
* You don ’ t want to use this function to decide whether text can be
* inserted at @ iter , because for insertion you don ’ t want to know
* whether the char at @ iter is inside an editable range , you want to
* know whether a new character inserted at @ iter would be inside an
* editable range . Use [ method @ Gtk . TextIter . can_insert ] to handle this
* case .
*
* Returns : whether @ iter is inside an editable range
*/
gboolean
gtk_text_iter_editable (const GtkTextIter *iter,
gboolean default_setting)
{
GtkTextAttributes *values;
gboolean retval;
g_return_val_if_fail (iter != NULL, FALSE );
values = gtk_text_attributes_new ();
values->editable = default_setting;
gtk_text_iter_get_attributes (iter, values);
retval = values->editable;
gtk_text_attributes_unref (values);
return retval;
}
/**
* gtk_text_iter_can_insert :
* @ iter : an iterator
* @ default_editability : % TRUE if text is editable by default
*
* Considering the default editability of the buffer , and tags that
* affect editability , determines whether text inserted at @ iter would
* be editable .
*
* If text inserted at @ iter would be editable then the
* user should be allowed to insert text at @ iter .
* [ method @ Gtk . TextBuffer . insert_interactive ] uses this function
* to decide whether insertions are allowed at a given position .
*
* Returns : whether text inserted at @ iter would be editable
*/
gboolean
gtk_text_iter_can_insert (const GtkTextIter *iter,
gboolean default_editability)
{
g_return_val_if_fail (iter != NULL, FALSE );
if (gtk_text_iter_editable (iter, default_editability))
return TRUE ;
/* If at start/end of buffer, default editability is used */
else if ((gtk_text_iter_is_start (iter) ||
gtk_text_iter_is_end (iter)) &&
default_editability)
return TRUE ;
else
{
/* if iter isn't editable, and the char before iter is,
* then iter is the first char in an editable region
* and thus insertion at iter results in editable text .
*/
GtkTextIter prev = *iter;
gtk_text_iter_backward_char (&prev);
return gtk_text_iter_editable (&prev, default_editability);
}
}
gboolean
gtk_text_iter_get_attributes (const GtkTextIter *iter,
GtkTextAttributes *values)
{
GPtrArray *tags;
/* Get the tags at this spot */
tags = _gtk_text_btree_get_tags (iter);
/* No tags, use default style */
if (tags == NULL || tags->len == 0 )
{
if (tags)
g_ptr_array_unref (tags);
return FALSE ;
}
_gtk_text_attributes_fill_from_tags (values, tags);
g_ptr_array_unref (tags);
return TRUE ;
}
/**
* gtk_text_iter_get_language :
* @ iter : an iterator
*
* Returns the language in effect at @ iter .
*
* If no tags affecting language apply to @ iter , the return
* value is identical to that of [ func @ Gtk . get_default_language ] .
*
* Returns : ( transfer full ) : language in effect at @ iter
*/
PangoLanguage *
gtk_text_iter_get_language (const GtkTextIter *iter)
{
GtkTextAttributes *values;
PangoLanguage *retval;
values = gtk_text_attributes_new ();
gtk_text_iter_get_attributes (iter, values);
retval = values->language;
gtk_text_attributes_unref (values);
return retval;
}
/**
* gtk_text_iter_starts_line :
* @ iter : an iterator
*
* Returns % TRUE if @ iter begins a paragraph .
*
* This is the case if [ method @ Gtk . TextIter . get_line_offset ]
* would return 0 . However this function is potentially more
* efficient than [ method @ Gtk . TextIter . get_line_offset ] , because
* it doesn ’ t have to compute the offset , it just has to see
* whether it ’ s 0 .
*
* Returns : whether @ iter begins a line
*/
gboolean
gtk_text_iter_starts_line (const GtkTextIter *iter)
{
GtkTextRealIter *real;
g_return_val_if_fail (iter != NULL, FALSE );
real = gtk_text_iter_make_surreal (iter);
if (real == NULL)
return FALSE ;
check_invariants (iter);
if (real->line_byte_offset >= 0 )
{
return (real->line_byte_offset == 0 );
}
else
{
g_assert (real->line_char_offset >= 0 );
return (real->line_char_offset == 0 );
}
}
/**
* 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 . The end
* iterator is considered to be at the end of a line , even though there
* are no paragraph delimiter chars there .
*
* Returns : whether @ iter is at the end of a line
*/
gboolean
gtk_text_iter_ends_line (const GtkTextIter *iter)
{
gunichar wc;
g_return_val_if_fail (iter != NULL, FALSE );
check_invariants (iter);
/* Only one character has type G_UNICODE_PARAGRAPH_SEPARATOR in
* Unicode 3 . 0 ; update this if that changes .
*/
#define PARAGRAPH_SEPARATOR 0 x2029
wc = gtk_text_iter_get_char (iter);
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 .
* 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 . . .
*/
if (gtk_text_iter_get_line_offset (&tmp) == 0 )
return TRUE ;
if (!gtk_text_iter_backward_char (&tmp))
return TRUE ;
return gtk_text_iter_get_char (&tmp) != '\r' ;
}
else
return FALSE ;
}
/**
* gtk_text_iter_is_end :
* @ iter : an iterator
*
* Returns % TRUE if @ iter is the end iterator .
*
* This means it is one past the last dereferenceable iterator
* in the buffer . [ method @ Gtk . TextIter . is_end ] is the most efficient
* way to check whether an iterator is the end iterator .
*
* Returns : whether @ iter is the end iterator
*/
gboolean
gtk_text_iter_is_end (const GtkTextIter *iter)
{
GtkTextRealIter *real;
g_return_val_if_fail (iter != NULL, FALSE );
real = gtk_text_iter_make_surreal (iter);
if (real == NULL)
return FALSE ;
check_invariants (iter);
if (!_gtk_text_line_contains_end_iter (real->line, real->tree))
return FALSE ;
/* Now we need the segments validated */
real = gtk_text_iter_make_real (iter);
if (real == NULL)
return FALSE ;
return _gtk_text_btree_is_end (real->tree, real->line,
real->segment,
real->segment_byte_offset,
real->segment_char_offset);
}
/**
* gtk_text_iter_is_start :
* @ iter : an iterator
*
* Returns % TRUE if @ iter is the first iterator in the buffer .
*
* Returns : whether @ iter is the first in the buffer
*/
gboolean
gtk_text_iter_is_start (const GtkTextIter *iter)
{
return gtk_text_iter_get_offset (iter) == 0 ;
}
/**
* gtk_text_iter_get_chars_in_line :
* @ iter : an iterator
*
* Returns the number of characters in the line containing @ iter ,
* including the paragraph delimiters .
*
* Returns : number of characters in the line
*/
int
gtk_text_iter_get_chars_in_line (const GtkTextIter *iter)
{
GtkTextRealIter *real;
int count;
GtkTextLineSegment *seg;
g_return_val_if_fail (iter != NULL, 0 );
real = gtk_text_iter_make_surreal (iter);
if (real == NULL)
return 0 ;
check_invariants (iter);
if (real->line_char_offset >= 0 )
{
/* We can start at the segments we've already found. */
count = real->line_char_offset - real->segment_char_offset;
seg = _gtk_text_iter_get_indexable_segment (iter);
}
else
{
/* count whole line. */
seg = real->line->segments;
count = 0 ;
}
while (seg != NULL)
{
count += seg->char_count;
seg = seg->next;
}
if (_gtk_text_line_contains_end_iter (real->line, real->tree))
count -= 1 ; /* Dump the newline that was in the last segment of the end iter line */
return count;
}
/**
* gtk_text_iter_get_bytes_in_line :
* @ iter : an iterator
*
* Returns the number of bytes in the line containing @ iter ,
* including the paragraph delimiters .
*
* Returns : number of bytes in the line
*/
int
gtk_text_iter_get_bytes_in_line (const GtkTextIter *iter)
{
GtkTextRealIter *real;
int count;
GtkTextLineSegment *seg;
g_return_val_if_fail (iter != NULL, 0 );
real = gtk_text_iter_make_surreal (iter);
if (real == NULL)
return 0 ;
check_invariants (iter);
if (real->line_byte_offset >= 0 )
{
/* We can start at the segments we've already found. */
count = real->line_byte_offset - real->segment_byte_offset;
seg = _gtk_text_iter_get_indexable_segment (iter);
}
else
{
/* count whole line. */
seg = real->line->segments;
count = 0 ;
}
while (seg != NULL)
{
count += seg->byte_count;
seg = seg->next;
}
if (_gtk_text_line_contains_end_iter (real->line, real->tree))
count -= 1 ; /* Dump the newline that was in the last segment of the end iter line */
return count;
}
/*
* Increments / decrements
*/
/* The return value of this indicates WHETHER WE MOVED.
* The return value of public functions indicates
* ( MOVEMENT OCCURRED & & NEW ITER IS DEREFERENCEABLE )
*
* This function will not change the iterator if
* it ’ s already on the last ( end iter ) line , i . e . it
* won ’ t move to the end of the last line .
*/
static gboolean
forward_line_leaving_caches_unmodified (GtkTextRealIter *real)
{
if (!_gtk_text_line_contains_end_iter (real->line, real->tree))
{
GtkTextLine *new_line;
new_line = _gtk_text_line_next (real->line);
g_assert (new_line);
g_assert (new_line != real->line);
g_assert (!_gtk_text_line_is_last (new_line, real->tree));
real->line = new_line;
real->line_byte_offset = 0 ;
real->line_char_offset = 0 ;
real->segment_byte_offset = 0 ;
real->segment_char_offset = 0 ;
/* Find first segments in new line */
real->any_segment = real->line->segments;
real->segment = real->any_segment;
while (real->segment->char_count == 0 )
real->segment = real->segment->next;
return TRUE ;
}
else
{
/* There is no way to move forward a line; we were already at
* the line containing the end iterator .
* However we may not be at the end iterator itself .
*/
return FALSE ;
}
}
#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)
{
GtkTextLine *new_line;
new_line = _gtk_text_line_previous (real->line);
g_assert (new_line != real->line);
if (new_line != NULL)
{
real->line = new_line;
real->line_byte_offset = 0 ;
real->line_char_offset = 0 ;
real->segment_byte_offset = 0 ;
real->segment_char_offset = 0 ;
/* Find first segments in new line */
real->any_segment = real->line->segments;
real->segment = real->any_segment;
while (real->segment->char_count == 0 )
real->segment = real->segment->next;
return TRUE ;
}
else
{
/* There is no way to move backward; we were already
at the first line. */
/* We leave real->line as-is */
/* Note that we didn't clamp to the start of the first line. */
return FALSE ;
}
}
#endif
/* The return value indicates (MOVEMENT OCCURRED && NEW ITER IS
* DEREFERENCEABLE )
*/
static gboolean
forward_char (GtkTextRealIter *real)
{
GtkTextIter *iter = (GtkTextIter*)real;
check_invariants ((GtkTextIter*)real);
ensure_char_offsets (real);
if ( (real->segment_char_offset + 1 ) == real->segment->char_count)
{
/* Need to move to the next segment; if no next segment,
need to move to next line. */
return _gtk_text_iter_forward_indexable_segment (iter);
}
else
{
/* Just moving within a segment. Keep byte count
up-to-date, if it was already up-to-date. */
g_assert (real->segment->type == >k_text_char_type);
if (real->line_byte_offset >= 0 )
{
int bytes;
const char * start =
real->segment->body.chars + real->segment_byte_offset;
bytes = g_utf8_next_char (start) - start;
real->line_byte_offset += bytes;
real->segment_byte_offset += bytes;
g_assert (real->segment_byte_offset < real->segment->byte_count);
}
real->line_char_offset += 1 ;
real->segment_char_offset += 1 ;
adjust_char_index (real, 1 );
g_assert (real->segment_char_offset < real->segment->char_count);
/* We moved into the middle of a segment, so the any_segment
must now be the segment we're in the middle of. */
real->any_segment = real->segment;
check_invariants ((GtkTextIter*)real);
if (gtk_text_iter_is_end ((GtkTextIter*)real))
return FALSE ;
else
return TRUE ;
}
}
gboolean
_gtk_text_iter_forward_indexable_segment (GtkTextIter *iter)
{
/* Need to move to the next segment; if no next segment,
need to move to next line. */
GtkTextLineSegment *seg;
GtkTextLineSegment *any_seg;
GtkTextRealIter *real;
int chars_skipped;
int bytes_skipped;
g_return_val_if_fail (iter != NULL, FALSE );
real = gtk_text_iter_make_real (iter);
if (real == NULL)
return FALSE ;
check_invariants (iter);
if (real->line_char_offset >= 0 )
{
chars_skipped = real->segment->char_count - real->segment_char_offset;
g_assert (chars_skipped > 0 );
}
else
chars_skipped = 0 ;
if (real->line_byte_offset >= 0 )
{
bytes_skipped = real->segment->byte_count - real->segment_byte_offset;
g_assert (bytes_skipped > 0 );
}
else
bytes_skipped = 0 ;
/* Get first segment of any kind */
any_seg = real->segment->next;
/* skip non-indexable segments, if any */
seg = any_seg;
while (seg != NULL && seg->char_count == 0 )
seg = seg->next;
if (seg != NULL)
{
real->any_segment = any_seg;
real->segment = seg;
if (real->line_byte_offset >= 0 )
{
g_assert (bytes_skipped > 0 );
real->segment_byte_offset = 0 ;
real->line_byte_offset += bytes_skipped;
}
if (real->line_char_offset >= 0 )
{
g_assert (chars_skipped > 0 );
real->segment_char_offset = 0 ;
real->line_char_offset += chars_skipped;
adjust_char_index (real, chars_skipped);
}
check_invariants (iter);
return !gtk_text_iter_is_end (iter);
}
else
{
/* End of the line */
if (forward_line_leaving_caches_unmodified (real))
{
adjust_line_number (real, 1 );
if (real->line_char_offset >= 0 )
adjust_char_index (real, chars_skipped);
g_assert (real->line_byte_offset == 0 );
g_assert (real->line_char_offset == 0 );
g_assert (real->segment_byte_offset == 0 );
g_assert (real->segment_char_offset == 0 );
g_assert (gtk_text_iter_starts_line (iter));
check_invariants (iter);
return !gtk_text_iter_is_end (iter);
}
else
{
/* 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));
gtk_text_iter_forward_to_line_end (iter);
g_assert (gtk_text_iter_is_end (iter));
return FALSE ;
}
}
}
static gboolean
at_last_indexable_segment (GtkTextRealIter *real)
{
GtkTextLineSegment *seg;
/* Return TRUE if there are no indexable segments after
* this iterator .
*/
seg = real->segment->next;
while (seg)
{
if (seg->char_count > 0 )
return FALSE ;
seg = seg->next;
}
return TRUE ;
}
/* Goes back to the start of the next segment, even if
* we ’ re not at the start of the current segment ( always
* ends up on a different segment if it returns TRUE )
*/
gboolean
_gtk_text_iter_backward_indexable_segment (GtkTextIter *iter)
{
/* Move to the start of the previous segment; if no previous
* segment , to the last segment in the previous line . This is
* inherently a bit inefficient due to the singly - linked list and
* tree nodes , but we can ' t afford the RAM for doubly - linked .
*/
GtkTextRealIter *real;
GtkTextLineSegment *seg;
GtkTextLineSegment *any_seg;
GtkTextLineSegment *prev_seg;
GtkTextLineSegment *prev_any_seg;
int bytes_skipped;
int chars_skipped;
g_return_val_if_fail (iter != NULL, FALSE );
real = gtk_text_iter_make_real (iter);
if (real == NULL)
return FALSE ;
check_invariants (iter);
/* Find first segments in line */
any_seg = real->line->segments;
seg = any_seg;
while (seg->char_count == 0 )
seg = seg->next;
if (seg == real->segment)
{
/* Could probably do this case faster by hand-coding the
* iteration .
*/
/* We were already at the start of a line;
* go back to the previous line .
*/
if (gtk_text_iter_backward_line (iter))
{
/* Go forward to last indexable segment in line. */
while (!at_last_indexable_segment (real))
_gtk_text_iter_forward_indexable_segment (iter);
check_invariants (iter);
return TRUE ;
}
else
return FALSE ; /* We were at the start of the first line. */
}
/* We must be in the middle of a line; so find the indexable
* segment just before our current segment .
*/
g_assert (seg != real->segment);
do
{
prev_seg = seg;
prev_any_seg = any_seg;
any_seg = seg->next;
seg = any_seg;
while (seg->char_count == 0 )
seg = seg->next;
}
while (seg != real->segment);
g_assert (prev_seg != NULL);
g_assert (prev_any_seg != NULL);
g_assert (prev_seg->char_count > 0 );
/* We skipped the entire previous segment, plus any
* chars we were into the current segment .
*/
if (real->segment_byte_offset >= 0 )
bytes_skipped = prev_seg->byte_count + real->segment_byte_offset;
else
bytes_skipped = -1 ;
if (real->segment_char_offset >= 0 )
chars_skipped = prev_seg->char_count + real->segment_char_offset;
else
chars_skipped = -1 ;
real->segment = prev_seg;
real->any_segment = prev_any_seg;
real->segment_byte_offset = 0 ;
real->segment_char_offset = 0 ;
if (bytes_skipped >= 0 )
{
if (real->line_byte_offset >= 0 )
{
real->line_byte_offset -= bytes_skipped;
g_assert (real->line_byte_offset >= 0 );
}
}
else
real->line_byte_offset = -1 ;
if (chars_skipped >= 0 )
{
if (real->line_char_offset >= 0 )
{
real->line_char_offset -= chars_skipped;
g_assert (real->line_char_offset >= 0 );
}
if (real->cached_char_index >= 0 )
{
real->cached_char_index -= chars_skipped;
g_assert (real->cached_char_index >= 0 );
}
}
else
{
real->line_char_offset = -1 ;
real->cached_char_index = -1 ;
}
/* line number is unchanged. */
check_invariants (iter);
return TRUE ;
}
/**
* gtk_text_iter_forward_char :
* @ iter : an iterator
*
* Moves @ iter forward by one character offset .
*
* Note that images embedded in the buffer occupy 1 character slot , so
* this function 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 this function returns % FALSE for convenience when writing loops .
*
* Returns : whether @ iter moved and is dereferenceable
*/
gboolean
gtk_text_iter_forward_char (GtkTextIter *iter)
{
GtkTextRealIter *real;
g_return_val_if_fail (iter != NULL, FALSE );
real = gtk_text_iter_make_real (iter);
if (real == NULL)
return FALSE ;
else
{
check_invariants (iter);
return forward_char (real);
}
}
/**
* gtk_text_iter_backward_char :
* @ iter : an iterator
*
* Moves backward by one character offset .
*
* Returns % TRUE if movement was possible ; if @ iter was the first
* in the buffer ( character offset 0 ) , this function returns % FALSE
* for convenience when writing loops .
*
* Returns : whether movement was possible
*/
gboolean
gtk_text_iter_backward_char (GtkTextIter *iter)
{
g_return_val_if_fail (iter != NULL, FALSE );
check_invariants (iter);
return gtk_text_iter_backward_chars (iter, 1 );
}
/*
Definitely we should try to linear scan as often as possible for
movement within a single line , because we can ' t use the BTree to
speed within - line searches up ; for movement between lines , we would
like to avoid the linear scan probably .
Instead of using this constant , it might be nice to cache the line
length in the iterator and linear scan if motion is within a single
line .
I guess you ' d have to profile the various approaches .
*/
#define MAX_LINEAR_SCAN 150
/**
* gtk_text_iter_forward_chars :
* @ iter : an iterator
* @ count : number of characters to move , may be negative
*
* Moves @ count characters 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 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 .
*
* Returns : whether @ iter moved and is dereferenceable
*/
gboolean
gtk_text_iter_forward_chars (GtkTextIter *iter, int count)
{
GtkTextRealIter *real;
g_return_val_if_fail (iter != NULL, FALSE );
FIX_OVERFLOWS (count);
real = gtk_text_iter_make_real (iter);
if (real == NULL)
return FALSE ;
else if (count == 0 )
return FALSE ;
else if (count < 0 )
return gtk_text_iter_backward_chars (iter, 0 - count);
else if (count < MAX_LINEAR_SCAN)
{
check_invariants (iter);
while (count > 1 )
{
if (!forward_char (real))
return FALSE ;
--count;
}
return forward_char (real);
}
else
{
int current_char_index;
int new_char_index;
check_invariants (iter);
current_char_index = gtk_text_iter_get_offset (iter);
if (current_char_index == _gtk_text_btree_char_count (real->tree))
return FALSE ; /* can't move forward */
new_char_index = current_char_index + count;
gtk_text_iter_set_offset (iter, new_char_index);
check_invariants (iter);
/* Return FALSE if we're on the non-dereferenceable end
* iterator .
*/
if (gtk_text_iter_is_end (iter))
return FALSE ;
else
return TRUE ;
}
}
/**
* gtk_text_iter_backward_chars :
* @ iter : an iterator
* @ count : number of characters to move
*
* Moves @ count characters 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 .
*
* Returns : whether @ iter moved and is dereferenceable
*/
gboolean
gtk_text_iter_backward_chars (GtkTextIter *iter, int count)
{
GtkTextRealIter *real;
g_return_val_if_fail (iter != NULL, FALSE );
FIX_OVERFLOWS (count);
real = gtk_text_iter_make_real (iter);
if (real == NULL)
return FALSE ;
else if (count == 0 )
return FALSE ;
else if (count < 0 )
return gtk_text_iter_forward_chars (iter, 0 - count);
ensure_char_offsets (real);
check_invariants (iter);
/* <, 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);
if (real->line_byte_offset >= 0 )
{
const char *p;
int new_byte_offset;
/* 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);
check_invariants (iter);
return TRUE ;
}
else
{
/* We need to go back into previous segments. For now,
* just keep this really simple . FIXME
* use backward_indexable_segment .
*/
if (TRUE || count > MAX_LINEAR_SCAN)
{
int current_char_index;
int new_char_index;
current_char_index = gtk_text_iter_get_offset (iter);
if (current_char_index == 0 )
return FALSE ; /* can't move backward */
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);
return TRUE ;
}
else
{
/* FIXME backward_indexable_segment here */
return FALSE ;
}
}
}
#if 0
/* These two can't be implemented efficiently (always have to use
* a linear scan , since that ’ s the only way to find all the non - text
* segments )
*/
/**
* gtk_text_iter_forward_text_chars :
* @ iter : a ` GtkTextIter `
* @ count : number of chars to move
*
* Moves forward by @ count text characters .
*
* Paintables , widgets , etc . do not count as characters for this .
*
* Equivalent to moving through the results of gtk_text_iter_get_text ( ) ,
* rather than gtk_text_iter_get_slice ( ) .
*
* Returns : whether @ iter moved and is dereferenceable
*/
gboolean
gtk_text_iter_forward_text_chars (GtkTextIter *iter,
int count)
{
}
/**
* gtk_text_iter_backward_text_chars :
* @ iter : a ` GtkTextIter `
* @ count : number of chars to move
*
* Moves backward by @ count text characters ( paintables , widgets ,
* etc . do not count as characters for this ) . Equivalent to moving
* through the results of gtk_text_iter_get_text ( ) , rather than
* gtk_text_iter_get_slice ( ) .
*
* Returns : whether @ iter moved and is dereferenceable
**/
gboolean
gtk_text_iter_backward_text_chars (GtkTextIter *iter,
int count)
{
}
#endif
/**
* gtk_text_iter_forward_line :
* @ iter : an iterator
*
* 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
* dereferenceable , returns % FALSE . Otherwise , returns % TRUE .
*
* Returns : whether @ iter can be dereferenced
*/
gboolean
gtk_text_iter_forward_line (GtkTextIter *iter)
{
GtkTextRealIter *real;
g_return_val_if_fail (iter != NULL, FALSE );
real = gtk_text_iter_make_real (iter);
if (real == NULL)
return FALSE ;
check_invariants (iter);
if (forward_line_leaving_caches_unmodified (real))
{
invalidate_char_index (real);
adjust_line_number (real, 1 );
check_invariants (iter);
if (gtk_text_iter_is_end (iter))
return FALSE ;
else
return TRUE ;
}
else
{
/* On the last line, move to end of it */
if (!gtk_text_iter_is_end (iter))
gtk_text_iter_forward_to_end (iter);
check_invariants (iter);
return FALSE ;
}
}
/**
* gtk_text_iter_backward_line :
* @ iter : an iterator
*
* 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 , 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 . )
*
* Returns : whether @ iter moved
*/
gboolean
gtk_text_iter_backward_line (GtkTextIter *iter)
{
GtkTextLine *new_line;
GtkTextRealIter *real;
gboolean offset_will_change;
int offset;
g_return_val_if_fail (iter != NULL, FALSE );
real = gtk_text_iter_make_real (iter);
if (real == NULL)
return FALSE ;
ensure_char_offsets (real);
check_invariants (iter);
new_line = _gtk_text_line_previous (real->line);
offset_will_change = FALSE ;
if (real->line_char_offset > 0 )
offset_will_change = TRUE ;
if (new_line != NULL)
{
real->line = new_line;
adjust_line_number (real, -1 );
}
else
{
if (!offset_will_change)
return FALSE ;
}
invalidate_char_index (real);
real->line_byte_offset = 0 ;
real->line_char_offset = 0 ;
real->segment_byte_offset = 0 ;
real->segment_char_offset = 0 ;
/* Find first segment in line */
real->any_segment = real->line->segments;
real->segment = _gtk_text_line_byte_to_segment (real->line,
0 , &offset);
g_assert (offset == 0 );
/* Note that if we are on the first line, we snap to the start of
* the first line and return TRUE , so TRUE means the iterator
* changed , not that the line changed ; this is maybe a bit
* weird . I ' m not sure there ' s an obvious right thing to do though .
*/
check_invariants (iter);
return TRUE ;
}
/**
* gtk_text_iter_forward_lines :
* @ iter : a ` GtkTextIter `
* @ count : number of lines to move forward
*
* Moves @ count 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 .
*
* Returns : whether @ iter moved and is dereferenceable
*/
gboolean
gtk_text_iter_forward_lines (GtkTextIter *iter, int count)
{
FIX_OVERFLOWS (count);
if (count < 0 )
return gtk_text_iter_backward_lines (iter, 0 - count);
else if (count == 0 )
return FALSE ;
else if (count == 1 )
{
check_invariants (iter);
return gtk_text_iter_forward_line (iter);
}
else
{
int old_line;
if (gtk_text_iter_is_end (iter))
return FALSE ;
old_line = gtk_text_iter_get_line (iter);
gtk_text_iter_set_line (iter, old_line + count);
if ((gtk_text_iter_get_line (iter) - old_line) < count)
{
/* count went past the last line, so move to end of last line */
if (!gtk_text_iter_is_end (iter))
gtk_text_iter_forward_to_end (iter);
}
return !gtk_text_iter_is_end (iter);
}
}
/**
* gtk_text_iter_backward_lines :
* @ iter : a ` GtkTextIter `
* @ count : number of lines to move backward
*
* Moves @ count 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 .
*
* Returns : whether @ iter moved and is dereferenceable
*/
gboolean
gtk_text_iter_backward_lines (GtkTextIter *iter, int count)
{
FIX_OVERFLOWS (count);
if (count < 0 )
return gtk_text_iter_forward_lines (iter, 0 - count);
else if (count == 0 )
return FALSE ;
else if (count == 1 )
{
return gtk_text_iter_backward_line (iter);
}
else
{
int old_line;
old_line = gtk_text_iter_get_line (iter);
gtk_text_iter_set_line (iter, MAX (old_line - count, 0 ));
return (gtk_text_iter_get_line (iter) != old_line);
}
}
/**
* 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 .
*
* Returns : whether @ iter can be dereferenced
*/
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 . )
*
* Returns : whether @ iter moved
*/
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 .
*
* Returns : whether @ iter moved and is dereferenceable
*/
gboolean
gtk_text_iter_forward_visible_lines (GtkTextIter *iter,
int 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 .
*
* Returns : whether @ iter moved and is dereferenceable
*/
gboolean
gtk_text_iter_backward_visible_lines (GtkTextIter *iter,
int 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,
int offset,
int len,
int *found_offset,
gboolean already_moved_initially);
typedef gboolean (* TestLogAttrFunc) (const PangoLogAttr *attrs,
int offset,
int min_offset,
int len);
/* Word funcs */
static gboolean
find_word_end_func (const PangoLogAttr *attrs,
int offset,
int len,
int *found_offset,
gboolean already_moved_initially)
{
if (!already_moved_initially)
++offset;
/* Find end of next word */
while (offset <= len)
{
if (attrs[offset].is_word_end)
{
*found_offset = offset;
return TRUE ;
}
++offset;
}
return FALSE ;
}
static gboolean
is_word_end_func (const PangoLogAttr *attrs,
int offset,
int min_offset,
int len)
{
return attrs[offset].is_word_end;
}
static gboolean
find_word_start_func (const PangoLogAttr *attrs,
int offset,
int len,
int *found_offset,
gboolean already_moved_initially)
{
if (!already_moved_initially)
--offset;
/* Find start of prev word */
while (offset >= 0 )
{
if (attrs[offset].is_word_start)
{
*found_offset = offset;
return TRUE ;
}
--offset;
}
return FALSE ;
}
static gboolean
is_word_start_func (const PangoLogAttr *attrs,
int offset,
int min_offset,
int len)
{
return attrs[offset].is_word_start;
}
static gboolean
inside_word_func (const PangoLogAttr *attrs,
int offset,
int min_offset,
int len)
{
/* Find next word start or end */
while (offset >= min_offset &&
!(attrs[offset].is_word_start || attrs[offset].is_word_end))
--offset;
if (offset >= 0 )
return attrs[offset].is_word_start;
else
return FALSE ;
}
/* Sentence funcs */
static gboolean
find_sentence_end_func (const PangoLogAttr *attrs,
int offset,
int len,
int *found_offset,
gboolean already_moved_initially)
{
if (!already_moved_initially)
++offset;
/* Find end of next sentence */
while (offset <= len)
{
if (attrs[offset].is_sentence_end)
{
*found_offset = offset;
return TRUE ;
}
++offset;
}
return FALSE ;
}
static gboolean
is_sentence_end_func (const PangoLogAttr *attrs,
int offset,
int min_offset,
int len)
{
return attrs[offset].is_sentence_end;
}
static gboolean
find_sentence_start_func (const PangoLogAttr *attrs,
int offset,
int len,
int *found_offset,
gboolean already_moved_initially)
{
if (!already_moved_initially)
--offset;
/* Find start of prev sentence */
while (offset >= 0 )
{
if (attrs[offset].is_sentence_start)
{
*found_offset = offset;
return TRUE ;
}
--offset;
}
return FALSE ;
}
static gboolean
is_sentence_start_func (const PangoLogAttr *attrs,
int offset,
int min_offset,
int len)
{
return attrs[offset].is_sentence_start;
}
static gboolean
inside_sentence_func (const PangoLogAttr *attrs,
int offset,
int min_offset,
int len)
{
/* Find next sentence start or end */
while (!(attrs[offset].is_sentence_start || attrs[offset].is_sentence_end))
{
--offset;
if (offset < min_offset)
return FALSE ;
}
return attrs[offset].is_sentence_start;
}
static gboolean
test_log_attrs (const GtkTextIter *iter,
TestLogAttrFunc func)
{
int char_len;
const PangoLogAttr *attrs;
int offset;
g_return_val_if_fail (iter != NULL, FALSE );
attrs = _gtk_text_buffer_get_line_log_attrs (gtk_text_iter_get_buffer (iter),
iter, &char_len);
offset = gtk_text_iter_get_line_offset (iter);
g_assert (offset <= char_len);
return (* func) (attrs, offset, 0 , char_len);
}
static gboolean
find_line_log_attrs (const GtkTextIter *iter,
FindLogAttrFunc func,
int *found_offset,
gboolean already_moved_initially)
{
int char_len;
const PangoLogAttr *attrs;
int offset;
g_return_val_if_fail (iter != NULL, FALSE );
attrs = _gtk_text_buffer_get_line_log_attrs (gtk_text_iter_get_buffer (iter),
iter, &char_len);
offset = gtk_text_iter_get_line_offset (iter);
return (* func) (attrs,
offset,
char_len,
found_offset,
already_moved_initially);
}
static gboolean
find_by_log_attrs (GtkTextIter *arg_iter,
FindLogAttrFunc func,
gboolean forward)
{
GtkTextIter iter;
gboolean already_moved_initially = FALSE ;
g_return_val_if_fail (arg_iter != NULL, FALSE );
iter = *arg_iter;
while (TRUE )
{
int offset = 0 ;
gboolean found;
found = find_line_log_attrs (&iter, func, &offset, already_moved_initially);
if (found)
{
gboolean moved;
gtk_text_iter_set_line_offset (&iter, offset);
moved = !gtk_text_iter_equal (&iter, arg_iter);
*arg_iter = iter;
return moved && !gtk_text_iter_is_end (arg_iter);
}
if (forward)
{
if (!gtk_text_iter_forward_line (&iter))
return FALSE ;
already_moved_initially = TRUE ;
}
else
{
/* Go to end of previous line. First go to the current line offset 0,
* because backward_line ( ) snaps to start of line 0 if iter is already
* on line 0 .
*/
gtk_text_iter_set_line_offset (&iter, 0 );
if (gtk_text_iter_backward_line (&iter))
{
if (!gtk_text_iter_ends_line (&iter))
gtk_text_iter_forward_to_line_end (&iter);
already_moved_initially = TRUE ;
}
else
{
return FALSE ;
}
}
}
}
static gboolean
find_visible_by_log_attrs (GtkTextIter *iter,
FindLogAttrFunc func,
gboolean forward)
{
GtkTextIter pos;
g_return_val_if_fail (iter != NULL, FALSE );
pos = *iter;
while (TRUE )
{
GtkTextIter pos_before = pos;
find_by_log_attrs (&pos, func, forward);
if (gtk_text_iter_equal (&pos_before, &pos))
break ;
if (!_gtk_text_btree_char_is_invisible (&pos))
{
*iter = pos;
return !gtk_text_iter_is_end (iter);
}
}
return FALSE ;
}
typedef gboolean (* OneStepFunc) (GtkTextIter *iter);
typedef gboolean (* MultipleStepFunc) (GtkTextIter *iter, int count);
static gboolean
move_multiple_steps (GtkTextIter *iter,
int 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 `
*
* Moves forward to the next 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 .
*
* Returns : % TRUE if @ iter moved and is not the end iterator
*/
gboolean
gtk_text_iter_forward_word_end (GtkTextIter *iter)
{
return find_by_log_attrs (iter, find_word_end_func, TRUE );
}
/**
* gtk_text_iter_backward_word_start :
* @ iter : a ` GtkTextIter `
*
* 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
*
* Returns : % TRUE if @ iter moved and is not the end iterator
*/
gboolean
gtk_text_iter_backward_word_start (GtkTextIter *iter)
{
return find_by_log_attrs (iter, find_word_start_func, FALSE );
}
/* FIXME a loop around a truly slow function means
* a truly spectacularly slow function .
*/
/**
* gtk_text_iter_forward_word_ends :
* @ iter : a ` GtkTextIter `
* @ count : number of times to move
*
* Calls [ method @ Gtk . TextIter . forward_word_end ] up to @ count times .
*
* Returns : % TRUE if @ iter moved and is not the end iterator
*/
gboolean
gtk_text_iter_forward_word_ends (GtkTextIter *iter,
int count)
{
return move_multiple_steps (iter, count,
gtk_text_iter_forward_word_end,
gtk_text_iter_backward_word_starts);
}
/**
* gtk_text_iter_backward_word_starts :
* @ iter : a ` GtkTextIter `
* @ count : number of times to move
*
* Calls [ method @ Gtk . TextIter . backward_word_start ] up to @ count times .
*
* Returns : % TRUE if @ iter moved and is not the end iterator
*/
gboolean
gtk_text_iter_backward_word_starts (GtkTextIter *iter,
int count)
{
return move_multiple_steps (iter, count,
gtk_text_iter_backward_word_start,
gtk_text_iter_forward_word_ends);
}
/**
* 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
*
* Returns : % TRUE if @ iter moved and is not the end iterator
*/
gboolean
gtk_text_iter_forward_visible_word_end (GtkTextIter *iter)
{
return find_visible_by_log_attrs (iter, find_word_end_func, TRUE );
}
/**
* 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 .
*
* Returns : % TRUE if @ iter moved and is not the end iterator
*/
gboolean
gtk_text_iter_backward_visible_word_start (GtkTextIter *iter)
{
return find_visible_by_log_attrs (iter, find_word_start_func, FALSE );
}
/**
* gtk_text_iter_forward_visible_word_ends :
* @ iter : a ` GtkTextIter `
* @ count : number of times to move
*
* Calls [ method @ Gtk . TextIter . forward_visible_word_end ] up to @ count times .
*
* Returns : % TRUE if @ iter moved and is not the end iterator
*/
gboolean
gtk_text_iter_forward_visible_word_ends (GtkTextIter *iter,
int 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 [ method @ Gtk . TextIter . backward_visible_word_start ] up to @ count times .
*
* Returns : % TRUE if @ iter moved and is not the end iterator
*/
gboolean
gtk_text_iter_backward_visible_word_starts (GtkTextIter *iter,
int count)
{
return move_multiple_steps (iter, count,
gtk_text_iter_backward_visible_word_start,
gtk_text_iter_forward_visible_word_ends);
}
/**
* gtk_text_iter_starts_word :
* @ iter : a ` GtkTextIter `
*
* Determines whether @ iter begins a natural - language word .
*
* Word breaks are determined by Pango and should be correct
* for nearly any language .
*
* Returns : % TRUE if @ iter is at the start of a word
*/
gboolean
gtk_text_iter_starts_word (const GtkTextIter *iter)
{
return test_log_attrs (iter, is_word_start_func);
}
/**
* gtk_text_iter_ends_word :
* @ iter : a ` GtkTextIter `
*
* Determines whether @ iter ends a natural - language word .
*
* Word breaks are determined by Pango and should be correct
* for nearly any language .
*
* Returns : % TRUE if @ iter is at the end of a word
*/
gboolean
gtk_text_iter_ends_word (const GtkTextIter *iter)
{
return test_log_attrs (iter, is_word_end_func);
}
/**
* gtk_text_iter_inside_word :
* @ iter : a ` GtkTextIter `
*
* Determines whether the character pointed by @ iter is part of a
* natural - language word ( as opposed to say inside some whitespace ) .
*
* Word breaks are determined by Pango and should be correct
* for nearly any language .
*
* Note that if [ method @ Gtk . TextIter . starts_word ] returns % TRUE ,
* then this function returns % TRUE too , since @ iter points to
* the first character of the word .
*
* Returns : % TRUE if @ iter is inside a word
*/
gboolean
gtk_text_iter_inside_word (const GtkTextIter *iter)
{
return test_log_attrs (iter, inside_word_func);
}
/**
* gtk_text_iter_starts_sentence :
* @ iter : a ` GtkTextIter `
*
* Determines whether @ iter begins a sentence .
*
* Sentence boundaries are determined by Pango and
* should be correct for nearly any language .
*
* Returns : % TRUE if @ iter is at the start of a sentence .
*/
gboolean
gtk_text_iter_starts_sentence (const GtkTextIter *iter)
{
return test_log_attrs (iter, is_sentence_start_func);
}
/**
* gtk_text_iter_ends_sentence :
* @ iter : a ` GtkTextIter `
*
* Determines whether @ iter ends a sentence .
*
* Sentence boundaries are determined by Pango and should
* be correct for nearly any language .
*
* Returns : % TRUE if @ iter is at the end of a sentence .
*/
gboolean
gtk_text_iter_ends_sentence (const GtkTextIter *iter)
{
return test_log_attrs (iter, is_sentence_end_func);
}
/**
* gtk_text_iter_inside_sentence :
* @ iter : a ` GtkTextIter `
*
* Determines whether @ iter is inside a sentence ( as opposed to in
* between two sentences , e . g . after a period and before the first
* letter of the next sentence ) .
*
* Sentence boundaries are determined by Pango and should be correct
* for nearly any language .
*
* Returns : % TRUE if @ iter is inside a sentence .
*/
gboolean
gtk_text_iter_inside_sentence (const GtkTextIter *iter)
{
return test_log_attrs (iter, inside_sentence_func);
}
/**
* gtk_text_iter_forward_sentence_end :
* @ iter : a ` GtkTextIter `
*
* Moves forward to the next sentence end .
*
* If @ iter is at the end of a sentence , moves to the next
* end of sentence .
*
* Sentence boundaries are determined by Pango and should
* be correct for nearly any language .
*
* Returns : % TRUE if @ iter moved and is not the end iterator
*/
gboolean
gtk_text_iter_forward_sentence_end (GtkTextIter *iter)
{
return find_by_log_attrs (iter, find_sentence_end_func, TRUE );
}
/**
* gtk_text_iter_backward_sentence_start :
* @ iter : a ` GtkTextIter `
*
* 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 .
*
* Returns : % TRUE if @ iter moved and is not the end iterator
*/
gboolean
gtk_text_iter_backward_sentence_start (GtkTextIter *iter)
{
return find_by_log_attrs (iter, find_sentence_start_func, FALSE );
}
/* FIXME a loop around a truly slow function means
* a truly spectacularly slow function .
*/
/**
* gtk_text_iter_forward_sentence_ends :
* @ iter : a ` GtkTextIter `
* @ count : number of sentences to move
*
* Calls [ method @ Gtk . TextIter . forward_sentence_end ] @ count times .
*
* If @ count is negative , moves backward instead of forward .
*
* Returns : % TRUE if @ iter moved and is not the end iterator
*/
gboolean
gtk_text_iter_forward_sentence_ends (GtkTextIter *iter,
int count)
{
return move_multiple_steps (iter, count,
gtk_text_iter_forward_sentence_end,
gtk_text_iter_backward_sentence_starts);
}
/**
* gtk_text_iter_backward_sentence_starts :
* @ iter : a ` GtkTextIter `
* @ count : number of sentences to move
*
* Calls [ method @ Gtk . TextIter . backward_sentence_start ] up to @ count times .
*
* If @ count is negative , moves forward instead of backward .
*
* Returns : % TRUE if @ iter moved and is not the end iterator
*/
gboolean
gtk_text_iter_backward_sentence_starts (GtkTextIter *iter,
int count)
{
return move_multiple_steps (iter, count,
gtk_text_iter_backward_sentence_start,
gtk_text_iter_forward_sentence_ends);
}
static gboolean
find_forward_cursor_pos_func (const PangoLogAttr *attrs,
int offset,
int len,
int *found_offset,
gboolean already_moved_initially)
{
if (!already_moved_initially)
++offset;
while (offset <= len)
{
if (attrs[offset].is_cursor_position)
{
*found_offset = offset;
return TRUE ;
}
++offset;
}
return FALSE ;
}
static gboolean
find_backward_cursor_pos_func (const PangoLogAttr *attrs,
int offset,
int len,
int *found_offset,
gboolean already_moved_initially)
{
if (!already_moved_initially)
--offset;
while (offset >= 0 )
{
if (attrs[offset].is_cursor_position)
{
*found_offset = offset;
return TRUE ;
}
--offset;
}
return FALSE ;
}
static gboolean
is_cursor_pos_func (const PangoLogAttr *attrs,
int offset,
int min_offset,
int len)
{
return attrs[offset].is_cursor_position;
}
/**
* gtk_text_iter_forward_cursor_position :
* @ iter : a ` GtkTextIter `
*
* Moves @ iter forward by a single cursor position .
*
* Cursor positions are ( unsurprisingly ) positions where the
* cursor can appear . Perhaps surprisingly , there may not be
* a cursor position between all characters . The most common
* example for European languages would be a carriage return / newline
* sequence .
*
* For some Unicode characters , the equivalent of say the letter “ a ”
* with an accent mark will be represented as two characters , first
* the letter then a " combining mark " that causes the accent to be
* rendered ; so the cursor can ’ t go between those two characters .
*
* See also the [ struct @ Pango . LogAttr ] struct and the [ func @ Pango . break ]
* function .
*
* Returns : % TRUE if we moved and the new position is dereferenceable
*/
gboolean
gtk_text_iter_forward_cursor_position (GtkTextIter *iter)
{
return find_by_log_attrs (iter, find_forward_cursor_pos_func, TRUE );
}
/**
* gtk_text_iter_backward_cursor_position :
* @ iter : a ` GtkTextIter `
*
* Like [ method @ Gtk . TextIter . forward_cursor_position ] , but moves backward .
*
* Returns : % TRUE if we moved
*/
gboolean
gtk_text_iter_backward_cursor_position (GtkTextIter *iter)
{
return find_by_log_attrs (iter, find_backward_cursor_pos_func, FALSE );
}
/**
* gtk_text_iter_forward_cursor_positions :
* @ iter : a ` GtkTextIter `
* @ count : number of positions to move
*
* Moves up to @ count cursor positions .
*
* See [ method @ Gtk . TextIter . forward_cursor_position ] for details .
*
* Returns : % TRUE if we moved and the new position is dereferenceable
*/
gboolean
gtk_text_iter_forward_cursor_positions (GtkTextIter *iter,
int count)
{
return move_multiple_steps (iter, count,
gtk_text_iter_forward_cursor_position,
gtk_text_iter_backward_cursor_positions);
}
/**
* gtk_text_iter_backward_cursor_positions :
* @ iter : a ` GtkTextIter `
* @ count : number of positions to move
*
* Moves up to @ count cursor positions .
*
* See [ method @ Gtk . TextIter . forward_cursor_position ] for details .
*
* Returns : % TRUE if we moved and the new position is dereferenceable
*/
gboolean
gtk_text_iter_backward_cursor_positions (GtkTextIter *iter,
int count)
{
return move_multiple_steps (iter, count,
gtk_text_iter_backward_cursor_position,
gtk_text_iter_forward_cursor_positions);
}
/**
* gtk_text_iter_forward_visible_cursor_position :
* @ iter : a ` GtkTextIter `
*
* Moves @ iter forward to the next visible cursor position .
*
* See [ method @ Gtk . TextIter . forward_cursor_position ] for details .
*
* Returns : % TRUE if we moved and the new position is dereferenceable
*/
gboolean
gtk_text_iter_forward_visible_cursor_position (GtkTextIter *iter)
{
return find_visible_by_log_attrs (iter, find_forward_cursor_pos_func, TRUE );
}
/**
* gtk_text_iter_backward_visible_cursor_position :
* @ iter : a ` GtkTextIter `
*
* Moves @ iter backward to the previous visible cursor position .
*
* See [ method @ Gtk . TextIter . backward_cursor_position ] for details .
*
* Returns : % TRUE if we moved and the new position is dereferenceable
*/
gboolean
gtk_text_iter_backward_visible_cursor_position (GtkTextIter *iter)
{
return find_visible_by_log_attrs (iter, find_backward_cursor_pos_func, FALSE );
}
/**
* gtk_text_iter_forward_visible_cursor_positions :
* @ iter : a ` GtkTextIter `
* @ count : number of positions to move
*
* Moves up to @ count visible cursor positions .
*
* See [ method @ Gtk . TextIter . forward_cursor_position ] for details .
*
* Returns : % TRUE if we moved and the new position is dereferenceable
*/
gboolean
gtk_text_iter_forward_visible_cursor_positions (GtkTextIter *iter,
int 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 [ method @ Gtk . TextIter . backward_cursor_position ] for details .
*
* Returns : % TRUE if we moved and the new position is dereferenceable
*/
gboolean
gtk_text_iter_backward_visible_cursor_positions (GtkTextIter *iter,
int count)
{
return move_multiple_steps (iter, count,
gtk_text_iter_backward_visible_cursor_position,
gtk_text_iter_forward_visible_cursor_positions);
}
/**
* gtk_text_iter_is_cursor_position :
* @ iter : a ` GtkTextIter `
*
* Determine if @ iter is at a cursor position .
*
* See [ method @ Gtk . TextIter . forward_cursor_position ] or
* [ struct @ Pango . LogAttr ] or [ func @ Pango . break ] for details
* on what a cursor position is .
*
* Returns : % TRUE if the cursor can be placed at @ iter
*/
gboolean
gtk_text_iter_is_cursor_position (const GtkTextIter *iter)
{
return test_log_attrs (iter, is_cursor_pos_func);
}
/**
* gtk_text_iter_set_line_offset :
* @ iter : a ` GtkTextIter `
* @ char_on_line : a character offset relative to the start of @ iter ’ s current line
*
* Moves @ iter within a line , to a new character ( not byte ) offset .
*
* The given character offset must be less than or equal to the number
* of characters in the line ; if equal , @ iter moves to the start of the
* next line . See [ method @ Gtk . TextIter . set_line_index ] if you have a byte
* index rather than a character offset .
*/
void
gtk_text_iter_set_line_offset (GtkTextIter *iter,
int char_on_line)
{
GtkTextRealIter *real;
int chars_in_line;
g_return_if_fail (iter != NULL);
real = gtk_text_iter_make_surreal (iter);
if (real == NULL)
return ;
check_invariants (iter);
chars_in_line = gtk_text_iter_get_chars_in_line (iter);
g_return_if_fail (char_on_line <= chars_in_line);
if (char_on_line < chars_in_line)
iter_set_from_char_offset (real, real->line, char_on_line);
else
gtk_text_iter_forward_line (iter); /* set to start of next line */
check_invariants (iter);
}
/**
* gtk_text_iter_set_line_index :
* @ iter : a ` GtkTextIter `
* @ byte_on_line : a byte index relative to the start of @ iter ’ s current line
*
* Same as [ method @ Gtk . TextIter . set_line_offset ] , but works with a
* byte index . The given byte index must be at
* the start of a character , it can ’ t be in the middle of a UTF - 8
* encoded character .
*/
void
gtk_text_iter_set_line_index (GtkTextIter *iter,
int byte_on_line)
{
GtkTextRealIter *real;
int bytes_in_line;
g_return_if_fail (iter != NULL);
real = gtk_text_iter_make_surreal (iter);
if (real == NULL)
return ;
check_invariants (iter);
bytes_in_line = gtk_text_iter_get_bytes_in_line (iter);
g_return_if_fail (byte_on_line <= bytes_in_line);
if (byte_on_line < bytes_in_line)
iter_set_from_byte_offset (real, real->line, byte_on_line);
else
gtk_text_iter_forward_line (iter);
if (real->segment->type == >k_text_char_type &&
(real->segment->body.chars[real->segment_byte_offset] & 0 xc0) == 0 x80)
g_warning ("%s: Incorrect byte offset %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);
check_invariants (iter);
}
/**
* gtk_text_iter_set_visible_line_offset :
* @ iter : a ` GtkTextIter `
* @ char_on_line : a character offset
*
* Like [ method @ Gtk . TextIter . set_line_offset ] , but the offset is in visible
* characters , i . e . text with a tag making it invisible is not
* counted in the offset .
*/
void
gtk_text_iter_set_visible_line_offset (GtkTextIter *iter,
int char_on_line)
{
int chars_seen = 0 ;
GtkTextIter pos;
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 (chars_seen < char_on_line)
{
if (!_gtk_text_btree_char_is_invisible (&pos))
++chars_seen;
if (!gtk_text_iter_forward_char (&pos))
break ;
if (chars_seen == char_on_line)
break ;
}
if (_gtk_text_iter_get_text_line (&pos) == _gtk_text_iter_get_text_line (iter))
*iter = pos;
else
gtk_text_iter_forward_line (iter);
}
/**
* gtk_text_iter_set_visible_line_index :
* @ iter : a ` GtkTextIter `
* @ byte_on_line : a byte index
*
* Like [ method @ Gtk . TextIter . set_line_index ] , but the index is in visible
* bytes , i . e . text with a tag making it invisible is not counted
* in the index .
*/
void
gtk_text_iter_set_visible_line_index (GtkTextIter *iter,
int byte_on_line)
{
GtkTextRealIter *real;
int offset = 0 ;
GtkTextIter pos;
GtkTextLineSegment *seg;
g_return_if_fail (iter != NULL);
gtk_text_iter_set_line_offset (iter, 0 );
pos = *iter;
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))
{
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;
}
offset += seg->byte_count;
_gtk_text_iter_forward_indexable_segment (&pos);
seg = _gtk_text_iter_get_indexable_segment (&pos);
}
if (byte_on_line == 0 )
*iter = pos;
else
gtk_text_iter_forward_line (iter);
}
/**
* gtk_text_iter_set_line :
* @ iter : a ` GtkTextIter `
* @ line_number : line number ( counted from 0 )
*
* Moves iterator @ iter to the start of the line @ line_number .
*
* If @ line_number is negative or larger than or equal to the number of lines
* in the buffer , moves @ iter to the start of the last line in the buffer .
*/
void
gtk_text_iter_set_line (GtkTextIter *iter,
int line_number)
{
GtkTextLine *line;
int real_line;
GtkTextRealIter *real;
g_return_if_fail (iter != NULL);
real = gtk_text_iter_make_surreal (iter);
if (real == NULL)
return ;
check_invariants (iter);
line = _gtk_text_btree_get_line_no_last (real->tree, line_number, &real_line);
iter_set_from_char_offset (real, line, 0 );
/* We might as well cache this, since we know it. */
real->cached_line_number = real_line;
check_invariants (iter);
}
/**
* gtk_text_iter_set_offset :
* @ iter : a ` GtkTextIter `
* @ char_offset : a character number
*
* 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,
int char_offset)
{
GtkTextLine *line;
GtkTextRealIter *real;
int line_start;
int real_char_index;
g_return_if_fail (iter != NULL);
real = gtk_text_iter_make_surreal (iter);
if (real == NULL)
return ;
check_invariants (iter);
if (real->cached_char_index >= 0 &&
real->cached_char_index == char_offset)
return ;
line = _gtk_text_btree_get_line_at_char (real->tree,
char_offset,
&line_start,
&real_char_index);
iter_set_from_char_offset (real, line, real_char_index - line_start);
/* Go ahead and cache this since we have it. */
real->cached_char_index = real_char_index;
check_invariants (iter);
}
/**
* gtk_text_iter_forward_to_end :
* @ iter : a ` GtkTextIter `
*
* Moves @ iter forward to the “ end iterator ” , which points
* one past the last valid character in the buffer .
*
* [ method @ Gtk . TextIter . get_char ] called on the end iterator
* returns 0 , which is convenient for writing loops .
*/
void
gtk_text_iter_forward_to_end (GtkTextIter *iter)
{
GtkTextBuffer *buffer;
GtkTextRealIter *real;
g_return_if_fail (iter != NULL);
real = gtk_text_iter_make_surreal (iter);
if (real == NULL)
return ;
buffer = _gtk_text_btree_get_buffer (real->tree);
gtk_text_buffer_get_end_iter (buffer, iter);
}
/* FIXME this and gtk_text_iter_forward_to_line_end() could be cleaned up
* and made faster . Look at iter_ends_line ( ) for inspiration , perhaps .
* If all else fails we could cache the para delimiter pos in the iter .
* I think forward_to_line_end ( ) actually gets called fairly often .
*/
static int
find_paragraph_delimiter_for_line (GtkTextIter *iter)
{
GtkTextIter end;
end = *iter;
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);
}
return gtk_text_iter_get_line_offset (&end);
}
/**
* gtk_text_iter_forward_to_line_end :
* @ iter : a ` GtkTextIter `
*
* Moves the iterator to point to the paragraph delimiter characters .
*
* The possible characters are either a newline , a carriage return ,
* a carriage return / newline in sequence , or the Unicode paragraph
* separator character .
*
* If the iterator is already at the paragraph delimiter
* characters , moves to the paragraph delimiter characters for the
* next line . If @ iter is on the last line in the buffer , which does
* not end in paragraph delimiters , moves to the end iterator ( end of
* the last line ) , and returns % FALSE .
*
* Returns : % TRUE if we moved and the new location is not the end iterator
*/
gboolean
gtk_text_iter_forward_to_line_end (GtkTextIter *iter)
{
int current_offset;
int new_offset;
g_return_val_if_fail (iter != NULL, FALSE );
current_offset = gtk_text_iter_get_line_offset (iter);
new_offset = find_paragraph_delimiter_for_line (iter);
if (current_offset < new_offset)
{
/* Move to end of this line. */
gtk_text_iter_set_line_offset (iter, new_offset);
return !gtk_text_iter_is_end (iter);
}
else
{
/* Move to end of next line. */
if (gtk_text_iter_forward_line (iter))
{
/* We don't want to move past all
* empty lines .
*/
if (!gtk_text_iter_ends_line (iter))
gtk_text_iter_forward_to_line_end (iter);
return !gtk_text_iter_is_end (iter);
}
else
return FALSE ;
}
}
/**
* gtk_text_iter_forward_to_tag_toggle :
* @ iter : a ` GtkTextIter `
* @ tag : ( nullable ) : a ` GtkTextTag `
*
* Moves forward to the next toggle ( on or off ) of the
* @ 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
* 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 .
*
* Returns : whether we found a tag toggle after @ iter
*/
gboolean
gtk_text_iter_forward_to_tag_toggle (GtkTextIter *iter,
GtkTextTag *tag)
{
GtkTextLine *next_line;
GtkTextLine *current_line;
GtkTextRealIter *real;
g_return_val_if_fail (iter != NULL, FALSE );
real = gtk_text_iter_make_real (iter);
if (real == NULL)
return FALSE ;
check_invariants (iter);
if (gtk_text_iter_is_end (iter))
return FALSE ;
current_line = real->line;
next_line = _gtk_text_line_next_could_contain_tag (current_line,
real->tree, tag);
while (_gtk_text_iter_forward_indexable_segment (iter))
{
/* If we went forward to a line that couldn't contain a toggle
for the tag , then skip forward to a line that could contain
it . This potentially skips huge hunks of the tree , so we
aren't a purely linear search. */
if (real->line != current_line)
{
if (next_line == NULL)
{
/* End of search. Set to end of buffer. */
_gtk_text_btree_get_end_iter (real->tree, iter);
return FALSE ;
}
if (real->line != next_line)
iter_set_from_byte_offset (real, next_line, 0 );
current_line = real->line;
next_line = _gtk_text_line_next_could_contain_tag (current_line,
real->tree,
tag);
}
if (gtk_text_iter_toggles_tag (iter, tag))
{
/* If there's a toggle here, it isn't indexable so
any_segment can't be the indexable segment. */
g_assert (real->any_segment != real->segment);
return TRUE ;
}
}
/* Check end iterator for tags */
if (gtk_text_iter_toggles_tag (iter, tag))
{
/* If there's a toggle here, it isn't indexable so
any_segment can't be the indexable segment. */
g_assert (real->any_segment != real->segment);
return TRUE ;
}
/* Reached end of buffer */
return FALSE ;
}
/**
* gtk_text_iter_backward_to_tag_toggle :
* @ iter : a ` GtkTextIter `
* @ tag : ( nullable ) : a ` GtkTextTag `
*
* Moves backward to the next toggle ( on or off ) of the
* @ 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
* 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 .
*
* Returns : whether we found a tag toggle before @ iter
*/
gboolean
gtk_text_iter_backward_to_tag_toggle (GtkTextIter *iter,
GtkTextTag *tag)
{
GtkTextLine *prev_line;
GtkTextLine *current_line;
GtkTextRealIter *real;
g_return_val_if_fail (iter != NULL, FALSE );
real = gtk_text_iter_make_real (iter);
if (real == NULL)
return FALSE ;
check_invariants (iter);
current_line = real->line;
prev_line = _gtk_text_line_previous_could_contain_tag (current_line,
real->tree, tag);
/* If we're at segment start, go to the previous segment;
* if mid - segment , snap to start of current segment .
*/
if (is_segment_start (real))
{
if (!_gtk_text_iter_backward_indexable_segment (iter))
return FALSE ;
}
else
{
ensure_char_offsets (real);
if (!gtk_text_iter_backward_chars (iter, real->segment_char_offset))
return FALSE ;
}
do
{
/* If we went backward to a line that couldn't contain a toggle
* for the tag , then skip backward further to a line that
* could contain it . This potentially skips huge hunks of the
* tree , so we aren ' t a purely linear search .
*/
if (real->line != current_line)
{
if (prev_line == NULL)
{
/* End of search. Set to start of buffer. */
_gtk_text_btree_get_iter_at_char (real->tree, iter, 0 );
return FALSE ;
}
if (real->line != prev_line)
{
/* Set to last segment in prev_line (could do this
* more quickly )
*/
iter_set_from_byte_offset (real, prev_line, 0 );
while (!at_last_indexable_segment (real))
_gtk_text_iter_forward_indexable_segment (iter);
}
current_line = real->line;
prev_line = _gtk_text_line_previous_could_contain_tag (current_line,
real->tree,
tag);
}
if (gtk_text_iter_toggles_tag (iter, tag))
{
/* If there's a toggle here, it isn't indexable so
* any_segment can ' t be the indexable segment .
*/
g_assert (real->any_segment != real->segment);
return TRUE ;
}
}
while (_gtk_text_iter_backward_indexable_segment (iter));
/* Reached front of buffer */
return FALSE ;
}
static gboolean
matches_pred (GtkTextIter *iter,
GtkTextCharPredicate pred,
gpointer user_data)
{
int ch;
ch = gtk_text_iter_get_char (iter);
return (*pred) (ch, user_data);
}
/**
* gtk_text_iter_forward_find_char :
* @ iter : a ` GtkTextIter `
* @ pred : ( scope call ) ( closure user_data ) : a function to be called on each character
* @ user_data : user data for @ pred
* @ limit : ( nullable ) : search limit
*
* Advances @ iter , calling @ pred on each character .
*
* If @ pred returns % TRUE , returns % TRUE and stops scanning .
* If @ pred never returns % TRUE , @ iter is set to @ limit if
* @ limit is non - % NULL , otherwise to the end iterator .
*
* Returns : whether a match was found
*/
gboolean
gtk_text_iter_forward_find_char (GtkTextIter *iter,
GtkTextCharPredicate pred,
gpointer user_data,
const GtkTextIter *limit)
{
g_return_val_if_fail (iter != NULL, FALSE );
g_return_val_if_fail (pred != NULL, FALSE );
if (limit &&
gtk_text_iter_compare (iter, limit) >= 0 )
return FALSE ;
while ((limit == NULL ||
!gtk_text_iter_equal (limit, iter)) &&
gtk_text_iter_forward_char (iter))
{
if (matches_pred (iter, pred, user_data))
return TRUE ;
}
return FALSE ;
}
/**
* gtk_text_iter_backward_find_char :
* @ iter : a ` GtkTextIter `
* @ pred : ( scope call ) ( closure user_data ) : function to be called on each character
* @ user_data : user data for @ pred
* @ limit : ( nullable ) : search limit
*
* Same as [ method @ Gtk . TextIter . forward_find_char ] ,
* but goes backward from @ iter .
*
* Returns : whether a match was found
*/
gboolean
gtk_text_iter_backward_find_char (GtkTextIter *iter,
GtkTextCharPredicate pred,
gpointer user_data,
const GtkTextIter *limit)
{
g_return_val_if_fail (iter != NULL, FALSE );
g_return_val_if_fail (pred != NULL, FALSE );
if (limit &&
gtk_text_iter_compare (iter, limit) <= 0 )
return FALSE ;
while ((limit == NULL ||
!gtk_text_iter_equal (limit, iter)) &&
gtk_text_iter_backward_char (iter))
{
if (matches_pred (iter, pred, user_data))
return TRUE ;
}
return FALSE ;
}
static void
forward_chars_with_skipping (GtkTextIter *iter,
int count,
gboolean skip_invisible,
gboolean skip_nontext,
gboolean skip_decomp)
{
int i;
g_return_if_fail (count >= 0 );
i = count;
while (i > 0 )
{
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 ;
if (!ignored &&
skip_invisible &&
_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 */
char *normal;
char *casefold;
char buffer[6 ];
int 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)
--i;
}
}
static const char *
pointer_from_offset_skipping_decomp (const char *str,
int offset)
{
char *casefold, *normal;
const char *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 char *string,
const char *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 char *
utf8_strcasestr (const char *haystack,
const char *needle)
{
gsize needle_len;
gsize haystack_len;
const char *ret = NULL;
char *p;
char *casefold;
char *caseless_haystack;
int 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 = (char *)haystack;
goto finally;
}
if (haystack_len < needle_len)
{
ret = NULL;
goto finally;
}
p = (char *)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 char *
utf8_strrcasestr (const char *haystack,
const char *needle)
{
gsize needle_len;
gsize haystack_len;
const char *ret = NULL;
char *p;
char *casefold;
char *caseless_haystack;
int 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 = (char *)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 (TRUE )
{
if (exact_prefix_cmp (p, needle, needle_len))
{
ret = pointer_from_offset_skipping_decomp (haystack, i);
break ;
}
if (p == caseless_haystack)
break ;
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 char *s1,
const char *s2,
gssize n1,
gssize n2)
{
char *casefold;
char *normalized_s1;
char *normalized_s2;
int len_s1;
int 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 char **lines,
gboolean visible_only,
gboolean slice,
gboolean case_insensitive,
GtkTextIter *match_start,
GtkTextIter *match_end)
{
GtkTextIter next;
char *line_text;
const char *found;
int offset;
if (*lines == NULL || **lines == '\0' )
{
if (match_start)
*match_start = *start;
if (match_end)
*match_end = *start;
return TRUE ;
}
next = *start;
gtk_text_iter_forward_line (&next);
/* No more text in buffer, but *lines is nonempty */
if (gtk_text_iter_equal (start, &next))
{
return FALSE ;
}
if (slice)
{
if (visible_only)
line_text = gtk_text_iter_get_visible_slice (start, &next);
else
line_text = gtk_text_iter_get_slice (start, &next);
}
else
{
if (visible_only)
line_text = gtk_text_iter_get_visible_text (start, &next);
else
line_text = gtk_text_iter_get_text (start, &next);
}
if (match_start) /* if this is the first line we're matching */
{
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 ((!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;
}
if (found == NULL)
{
g_free (line_text);
return FALSE ;
}
/* Get offset to start of search string */
offset = g_utf8_strlen (line_text, found - line_text);
next = *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;
/* Go to end of search string */
forward_chars_with_skipping (&next, g_utf8_strlen (*lines, -1 ),
visible_only, !slice, case_insensitive);
g_free (line_text);
++lines;
if (match_end)
*match_end = next;
/* pass NULL for match_start, since we don't need to find the
* start again .
*/
return lines_match (&next, lines, visible_only, slice, case_insensitive, NULL, match_end);
}
/* strsplit() that retains the delimiter as part of the string. */
static char **
strbreakup (const char *string,
const char *delimiter,
int max_tokens,
int *num_strings,
gboolean case_insensitive)
{
GSList *string_list = NULL, *slist;
char **str_array;
const char *s;
char *casefold, *new_string;
guint i, n = 1 ;
g_return_val_if_fail (string != NULL, NULL);
g_return_val_if_fail (delimiter != NULL, NULL);
if (max_tokens < 1 )
max_tokens = G_MAXINT;
s = strstr (string, delimiter);
if (s)
{
guint delimiter_len = strlen (delimiter);
do
{
guint len;
len = s - string + delimiter_len;
new_string = g_new (char , 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;
s = strstr (string, delimiter);
}
while (--max_tokens && s);
}
if (*string)
{
n++;
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 (char *, n);
i = n - 1 ;
str_array[i--] = NULL;
for (slist = string_list; slist; slist = slist->next)
str_array[i--] = slist->data;
g_slist_free (string_list);
if (num_strings != NULL)
*num_strings = n - 1 ;
return str_array;
}
/**
* gtk_text_iter_forward_search :
* @ iter : start of search
* @ str : a search string
* @ flags : flags affecting how the search is done
* @ match_start : ( out caller - allocates ) ( optional ) : return location for start of match
* @ match_end : ( out caller - allocates ) ( optional ) : return location for end of match
* @ limit : ( nullable ) : location of last possible @ match_end , 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 .
*
* @ match_start will never be set to a ` GtkTextIter ` located before @ iter ,
* even if there is a possible @ match_end after or at @ iter .
*
* Returns : whether a match was found
*/
gboolean
gtk_text_iter_forward_search (const GtkTextIter *iter,
const char *str,
GtkTextSearchFlags flags,
GtkTextIter *match_start,
GtkTextIter *match_end,
const GtkTextIter *limit)
{
char **lines = NULL;
GtkTextIter match;
gboolean retval = FALSE ;
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 );
if (limit &&
gtk_text_iter_compare (iter, limit) >= 0 )
return FALSE ;
if (*str == '\0' )
{
/* If we can move one char, return the empty string there */
match = *iter;
if (gtk_text_iter_forward_char (&match))
{
if (limit &&
gtk_text_iter_equal (&match, limit))
return FALSE ;
if (match_start)
*match_start = match;
if (match_end)
*match_end = match;
return TRUE ;
}
else
return FALSE ;
}
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 , NULL, case_insensitive);
search = *iter;
do
{
/* This loop has an inefficient worst-case, where
* gtk_text_iter_get_text ( ) is called repeatedly on
* a single line .
*/
GtkTextIter end;
if (limit &&
gtk_text_iter_compare (&search, limit) >= 0 )
break ;
if (lines_match (&search, (const char **)lines,
visible_only, slice, case_insensitive, &match, &end))
{
if (limit == NULL ||
(limit &&
gtk_text_iter_compare (&end, limit) <= 0 ))
{
retval = TRUE ;
if (match_start)
*match_start = match;
if (match_end)
*match_end = end;
}
break ;
}
}
while (gtk_text_iter_forward_line (&search));
g_strfreev ((char **)lines);
return retval;
}
static gboolean
vectors_equal_ignoring_trailing (char **vec1,
char **vec2,
gboolean case_insensitive)
{
/* Ignores trailing chars in vec2's last line */
char **i1, **i2;
i1 = vec1;
i2 = vec2;
while (*i1 && *i2)
{
int len1;
int len2;
if (!case_insensitive)
{
if (strcmp (*i1, *i2) != 0 )
{
if (*(i2 + 1 ) == NULL) /* if this is the last line */
{
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
{
len1 = strlen (*i1);
len2 = strlen (*i2);
if (!utf8_caselessnmatch (*i1, *i2, len1, len2))
{
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 ;
else
return TRUE ;
}
typedef struct _LinesWindow LinesWindow;
struct _LinesWindow
{
int n_lines;
char **lines;
GtkTextIter first_line_start;
GtkTextIter first_line_end;
guint slice : 1 ;
guint visible_only : 1 ;
};
static void
lines_window_init (LinesWindow *win,
const GtkTextIter *start)
{
int i;
GtkTextIter line_start;
GtkTextIter line_end;
/* If we start on line 1, there are 2 lines to search (0 and 1), so
* n_lines can be 2 .
*/
if (gtk_text_iter_is_start (start) ||
gtk_text_iter_get_line (start) + 1 < win->n_lines)
{
/* Already at the end, or not enough lines to match */
win->lines = g_new0 (char *, 1 );
*win->lines = NULL;
return ;
}
line_start = *start;
line_end = *start;
/* Move to start iter to start of line */
gtk_text_iter_set_line_offset (&line_start, 0 );
if (gtk_text_iter_equal (&line_start, &line_end))
{
/* we were already at the start; so go back one line */
gtk_text_iter_backward_line (&line_start);
}
win->first_line_start = line_start;
win->first_line_end = line_end;
win->lines = g_new0 (char *, win->n_lines + 1 );
i = win->n_lines - 1 ;
while (i >= 0 )
{
char *line_text;
if (win->slice)
{
if (win->visible_only)
line_text = gtk_text_iter_get_visible_slice (&line_start, &line_end);
else
line_text = gtk_text_iter_get_slice (&line_start, &line_end);
}
else
{
if (win->visible_only)
line_text = gtk_text_iter_get_visible_text (&line_start, &line_end);
else
line_text = gtk_text_iter_get_text (&line_start, &line_end);
}
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);
--i;
}
}
static gboolean
lines_window_back (LinesWindow *win)
{
GtkTextIter new_start;
char *line_text;
new_start = win->first_line_start;
if (!gtk_text_iter_backward_line (&new_start))
return FALSE ;
else
{
win->first_line_start = new_start;
win->first_line_end = new_start;
gtk_text_iter_forward_line (&win->first_line_end);
}
if (win->slice)
{
if (win->visible_only)
line_text = gtk_text_iter_get_visible_slice (&win->first_line_start,
&win->first_line_end);
else
line_text = gtk_text_iter_get_slice (&win->first_line_start,
&win->first_line_end);
}
else
{
if (win->visible_only)
line_text = gtk_text_iter_get_visible_text (&win->first_line_start,
&win->first_line_end);
else
line_text = gtk_text_iter_get_text (&win->first_line_start,
&win->first_line_end);
}
/* Move lines to make room for first line. */
memmove (win->lines + 1 , win->lines, win->n_lines * sizeof (char *));
*win->lines = line_text;
/* Free old last line and NULL-terminate */
g_clear_pointer (&win->lines[win->n_lines], g_free);
return TRUE ;
}
static void
lines_window_free (LinesWindow *win)
{
g_strfreev (win->lines);
}
/**
* gtk_text_iter_backward_search :
* @ iter : a ` GtkTextIter ` where the search begins
* @ str : search string
* @ flags : bitmask of flags affecting the search
* @ match_start : ( out caller - allocates ) ( optional ) : return location for start of match
* @ match_end : ( out caller - allocates ) ( optional ) : return location for end of match
* @ limit : ( nullable ) : location of last possible @ match_start , or % NULL for start of buffer
*
* Same as [ method @ Gtk . TextIter . forward_search ] , but moves backward .
*
* @ match_end will never be set to a ` GtkTextIter ` located after @ iter ,
* even if there is a possible @ match_start before or at @ iter .
*
* Returns : whether a match was found
*/
gboolean
gtk_text_iter_backward_search (const GtkTextIter *iter,
const char *str,
GtkTextSearchFlags flags,
GtkTextIter *match_start,
GtkTextIter *match_end,
const GtkTextIter *limit)
{
char **lines = NULL;
char **l;
int n_lines;
LinesWindow win;
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 );
if (limit &&
gtk_text_iter_compare (limit, iter) > 0 )
return FALSE ;
if (*str == '\0' )
{
/* If we can move one char, return the empty string there */
GtkTextIter match = *iter;
if (limit && gtk_text_iter_equal (limit, &match))
return FALSE ;
if (gtk_text_iter_backward_char (&match))
{
if (match_start)
*match_start = match;
if (match_end)
*match_end = match;
return TRUE ;
}
else
return FALSE ;
}
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 , &n_lines, case_insensitive);
win.n_lines = n_lines;
win.slice = slice;
win.visible_only = visible_only;
lines_window_init (&win, iter);
if (*win.lines == NULL)
goto out;
do
{
const char *first_line_match;
if (limit &&
gtk_text_iter_compare (limit, &win.first_line_end) > 0 )
{
/* We're now before the search limit, abort. */
goto out;
}
/* If there are multiple lines, the first line will
* end in ' \ n ' , so this will only match at the
* end of the first line , which is correct .
*/
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 ,
case_insensitive))
{
/* Match! */
int offset;
GtkTextIter start_tmp;
GtkTextIter end_tmp;
/* Offset to start of search string */
offset = g_utf8_strlen (*win.lines, first_line_match - *win.lines);
start_tmp = win.first_line_start;
forward_chars_with_skipping (&start_tmp, offset,
visible_only, !slice, FALSE );
if (limit &&
gtk_text_iter_compare (limit, &start_tmp) > 0 )
goto out; /* match was bogus */
if (match_start)
*match_start = start_tmp;
/* Go to end of search string */
offset = 0 ;
for (l = lines; *l != NULL; l++)
offset += g_utf8_strlen (*l, -1 );
end_tmp = start_tmp;
forward_chars_with_skipping (&end_tmp, offset,
visible_only, !slice, case_insensitive);
if (match_end)
*match_end = end_tmp;
retval = TRUE ;
goto out;
}
}
while (lines_window_back (&win));
out:
lines_window_free (&win);
g_strfreev (lines);
return retval;
}
/*
* Comparisons
*/
/**
* gtk_text_iter_equal :
* @ lhs : a ` GtkTextIter `
* @ rhs : another ` GtkTextIter `
*
* Tests whether two iterators are equal , using the fastest possible
* mechanism .
*
* This function is very fast ; you can expect it to perform
* better than e . g . getting the character offset for each
* iterator and comparing the offsets yourself . Also , it ’ s a
* bit faster than [ method @ Gtk . TextIter . compare ] .
*
* Returns : % TRUE if the iterators point to the same place in the buffer
**/
gboolean
gtk_text_iter_equal (const GtkTextIter *lhs,
const GtkTextIter *rhs)
{
GtkTextRealIter *real_lhs;
GtkTextRealIter *real_rhs;
real_lhs = (GtkTextRealIter*)lhs;
real_rhs = (GtkTextRealIter*)rhs;
check_invariants (lhs);
check_invariants (rhs);
if (real_lhs->line != real_rhs->line)
return FALSE ;
else if (real_lhs->line_byte_offset >= 0 &&
real_rhs->line_byte_offset >= 0 )
return real_lhs->line_byte_offset == real_rhs->line_byte_offset;
else
{
/* the ensure_char_offsets() calls do nothing if the char offsets
are already up-to-date. */
ensure_char_offsets (real_lhs);
ensure_char_offsets (real_rhs);
return real_lhs->line_char_offset == real_rhs->line_char_offset;
}
}
gboolean
_gtk_text_iter_same_line (const GtkTextIter *lhs,
const GtkTextIter *rhs)
{
GtkTextRealIter *real_lhs;
GtkTextRealIter *real_rhs;
real_lhs = gtk_text_iter_make_surreal (lhs);
real_rhs = gtk_text_iter_make_surreal (rhs);
if (real_lhs == NULL || real_rhs == NULL)
return FALSE ;
check_invariants (lhs);
check_invariants (rhs);
return real_lhs->line == real_rhs->line;
}
/**
* gtk_text_iter_compare :
* @ lhs : a ` GtkTextIter `
* @ rhs : another ` GtkTextIter `
*
* A qsort ( ) - style function that returns negative if @ lhs is less than
* @ rhs , positive if @ lhs is greater than @ rhs , and 0 if they ’ re equal .
*
* Ordering is in character offset order , i . e . the first character
* in the buffer is less than the second character in the buffer .
*
* Returns : - 1 if @ lhs is less than @ rhs , 1 if @ lhs is greater , 0 if they are equal
*/
int
gtk_text_iter_compare (const GtkTextIter *lhs,
const GtkTextIter *rhs)
{
GtkTextRealIter *real_lhs;
GtkTextRealIter *real_rhs;
real_lhs = gtk_text_iter_make_surreal (lhs);
real_rhs = gtk_text_iter_make_surreal (rhs);
if (real_lhs == NULL ||
real_rhs == NULL)
return -1 ; /* why not */
check_invariants (lhs);
check_invariants (rhs);
if (real_lhs->line == real_rhs->line)
{
int left_index, right_index;
if (real_lhs->line_byte_offset >= 0 &&
real_rhs->line_byte_offset >= 0 )
{
left_index = real_lhs->line_byte_offset;
right_index = real_rhs->line_byte_offset;
}
else
{
/* the ensure_char_offsets() calls do nothing if
the offsets are already up-to-date. */
ensure_char_offsets (real_lhs);
ensure_char_offsets (real_rhs);
left_index = real_lhs->line_char_offset;
right_index = real_rhs->line_char_offset;
}
if (left_index < right_index)
return -1 ;
else if (left_index > right_index)
return 1 ;
else
return 0 ;
}
else
{
int line1, line2;
line1 = gtk_text_iter_get_line (lhs);
line2 = gtk_text_iter_get_line (rhs);
if (line1 < line2)
return -1 ;
else if (line1 > line2)
return 1 ;
else
return 0 ;
}
}
/**
* gtk_text_iter_in_range :
* @ iter : a ` GtkTextIter `
* @ start : start of range
* @ end : end of range
*
* Checks whether @ iter falls in the range [ @ start , @ end ) .
*
* @ start and @ end must be in ascending order .
*
* Returns : % TRUE if @ iter is in the range
*/
gboolean
gtk_text_iter_in_range (const GtkTextIter *iter,
const GtkTextIter *start,
const GtkTextIter *end)
{
g_return_val_if_fail (iter != NULL, FALSE );
g_return_val_if_fail (start != NULL, FALSE );
g_return_val_if_fail (end != NULL, FALSE );
g_return_val_if_fail (gtk_text_iter_compare (start, end) <= 0 , FALSE );
return gtk_text_iter_compare (iter, start) >= 0 &&
gtk_text_iter_compare (iter, end) < 0 ;
}
/**
* gtk_text_iter_order :
* @ first : a ` GtkTextIter `
* @ second : another ` GtkTextIter `
*
* Swaps the value of @ first and @ second if @ second comes before
* @ first in the buffer .
*
* That is , ensures that @ first and @ second are in sequence .
* Most text buffer functions that take a range call this
* automatically on your behalf , so there ’ s no real reason to
* call it yourself in those cases . There are some exceptions ,
* such as [ method @ Gtk . TextIter . in_range ] , that expect a
* pre - sorted range .
*/
void
gtk_text_iter_order (GtkTextIter *first,
GtkTextIter *second)
{
g_return_if_fail (first != NULL);
g_return_if_fail (second != NULL);
if (gtk_text_iter_compare (first, second) > 0 )
{
GtkTextIter tmp;
tmp = *first;
*first = *second;
*second = tmp;
}
}
/*
* Init iterators from the BTree
*/
void
_gtk_text_btree_get_iter_at_char (GtkTextBTree *tree,
GtkTextIter *iter,
int char_index)
{
GtkTextRealIter *real = (GtkTextRealIter*)iter;
int real_char_index;
int line_start;
GtkTextLine *line;
g_return_if_fail (iter != NULL);
g_return_if_fail (tree != NULL);
line = _gtk_text_btree_get_line_at_char (tree, char_index,
&line_start, &real_char_index);
iter_init_from_char_offset (iter, tree, line, real_char_index - line_start);
real->cached_char_index = real_char_index;
check_invariants (iter);
}
void
_gtk_text_btree_get_iter_at_line_char (GtkTextBTree *tree,
GtkTextIter *iter,
int line_number,
int char_on_line)
{
GtkTextRealIter *real = (GtkTextRealIter*)iter;
GtkTextLine *line;
int real_line;
g_return_if_fail (iter != NULL);
g_return_if_fail (tree != NULL);
line = _gtk_text_btree_get_line_no_last (tree, line_number, &real_line);
iter_init_from_char_offset (iter, tree, line, char_on_line);
/* We might as well cache this, since we know it. */
real->cached_line_number = real_line;
check_invariants (iter);
}
void
_gtk_text_btree_get_iter_at_line_byte (GtkTextBTree *tree,
GtkTextIter *iter,
int line_number,
int byte_index)
{
GtkTextRealIter *real = (GtkTextRealIter*)iter;
GtkTextLine *line;
int real_line;
g_return_if_fail (iter != NULL);
g_return_if_fail (tree != NULL);
line = _gtk_text_btree_get_line_no_last (tree, line_number, &real_line);
iter_init_from_byte_offset (iter, tree, line, byte_index);
/* We might as well cache this, since we know it. */
real->cached_line_number = real_line;
check_invariants (iter);
}
void
_gtk_text_btree_get_iter_at_line (GtkTextBTree *tree,
GtkTextIter *iter,
GtkTextLine *line,
int byte_offset)
{
g_return_if_fail (iter != NULL);
g_return_if_fail (tree != NULL);
g_return_if_fail (line != NULL);
iter_init_from_byte_offset (iter, tree, line, byte_offset);
check_invariants (iter);
}
void
_gtk_text_btree_get_iter_at_line_ptr_char (GtkTextBTree *tree,
GtkTextIter *iter,
GtkTextLine *line,
int char_offset)
{
g_return_if_fail (iter != NULL);
g_return_if_fail (tree != NULL);
g_return_if_fail (line != NULL);
iter_init_from_char_offset (iter, tree, line, char_offset);
check_invariants (iter);
}
gboolean
_gtk_text_btree_get_iter_at_first_toggle (GtkTextBTree *tree,
GtkTextIter *iter,
GtkTextTag *tag)
{
GtkTextLine *line;
g_return_val_if_fail (iter != NULL, FALSE );
g_return_val_if_fail (tree != NULL, FALSE );
line = _gtk_text_btree_first_could_contain_tag (tree, tag);
if (line == NULL)
{
/* Set iter to last in tree */
_gtk_text_btree_get_end_iter (tree, iter);
check_invariants (iter);
return FALSE ;
}
else
{
iter_init_from_byte_offset (iter, tree, line, 0 );
if (!gtk_text_iter_toggles_tag (iter, tag))
gtk_text_iter_forward_to_tag_toggle (iter, tag);
check_invariants (iter);
return TRUE ;
}
}
gboolean
_gtk_text_btree_get_iter_at_last_toggle (GtkTextBTree *tree,
GtkTextIter *iter,
GtkTextTag *tag)
{
gboolean found;
g_return_val_if_fail (iter != NULL, FALSE );
g_return_val_if_fail (tree != NULL, FALSE );
_gtk_text_btree_get_end_iter (tree, iter);
if (gtk_text_iter_toggles_tag (iter, tag))
found = TRUE ;
else
found = gtk_text_iter_backward_to_tag_toggle (iter, tag);
check_invariants (iter);
return found;
}
gboolean
_gtk_text_btree_get_iter_at_mark_name (GtkTextBTree *tree,
GtkTextIter *iter,
const char *mark_name)
{
GtkTextMark *mark;
g_return_val_if_fail (iter != NULL, FALSE );
g_return_val_if_fail (tree != NULL, FALSE );
mark = _gtk_text_btree_get_mark_by_name (tree, mark_name);
if (mark == NULL)
return FALSE ;
else
{
_gtk_text_btree_get_iter_at_mark (tree, iter, mark);
check_invariants (iter);
return TRUE ;
}
}
void
_gtk_text_btree_get_iter_at_paintable (GtkTextBTree *tree,
GtkTextIter *iter,
GtkTextLineSegment *seg)
{
g_return_if_fail (iter != NULL);
g_return_if_fail (tree != NULL);
iter_init_from_segment (iter, tree,
seg->body.paintable.line, seg);
g_assert (seg->body.paintable.line == _gtk_text_iter_get_text_line (iter));
check_invariants (iter);
}
void
_gtk_text_btree_get_iter_at_mark (GtkTextBTree *tree,
GtkTextIter *iter,
GtkTextMark *mark)
{
GtkTextLineSegment *seg;
g_return_if_fail (iter != NULL);
g_return_if_fail (tree != NULL);
g_return_if_fail (GTK_IS_TEXT_MARK (mark));
seg = mark->segment;
iter_init_from_segment (iter, tree,
seg->body.mark.line, seg);
g_assert (seg->body.mark.line == _gtk_text_iter_get_text_line (iter));
check_invariants (iter);
}
void
_gtk_text_btree_get_iter_at_child_anchor (GtkTextBTree *tree,
GtkTextIter *iter,
GtkTextChildAnchor *anchor)
{
GtkTextLineSegment *seg;
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;
g_assert (seg->body.child.line != NULL);
iter_init_from_segment (iter, tree,
seg->body.child.line, seg);
g_assert (seg->body.child.line == _gtk_text_iter_get_text_line (iter));
check_invariants (iter);
}
void
_gtk_text_btree_get_end_iter (GtkTextBTree *tree,
GtkTextIter *iter)
{
g_return_if_fail (iter != NULL);
g_return_if_fail (tree != NULL);
_gtk_text_btree_get_iter_at_char (tree,
iter,
_gtk_text_btree_char_count (tree));
check_invariants (iter);
}
void
_gtk_text_iter_check (const GtkTextIter *iter)
{
const GtkTextRealIter *real = (const GtkTextRealIter*)iter;
int line_char_offset, line_byte_offset, seg_char_offset, seg_byte_offset;
GtkTextLineSegment *byte_segment = NULL;
GtkTextLineSegment *byte_any_segment = NULL;
GtkTextLineSegment *char_segment = NULL;
GtkTextLineSegment *char_any_segment = NULL;
gboolean segments_updated;
/* This function checks our class invariants for the Iter class. */
g_assert (sizeof (GtkTextIter) == sizeof (GtkTextRealIter));
if (real->chars_changed_stamp !=
_gtk_text_btree_get_chars_changed_stamp (real->tree))
g_error ("iterator check failed: invalid iterator" );
if (real->line_char_offset < 0 && real->line_byte_offset < 0 )
g_error ("iterator check failed: both char and byte offsets are invalid" );
segments_updated = (real->segments_changed_stamp ==
_gtk_text_btree_get_segments_changed_stamp (real->tree));
#if 0
printf ("checking iter, segments %s updated, byte %d char %d\n" ,
segments_updated ? "are" : "aren't" ,
real->line_byte_offset,
real->line_char_offset);
#endif
if (segments_updated)
{
if (real->segment_char_offset < 0 && real->segment_byte_offset < 0 )
g_error ("iterator check failed: both char and byte segment offsets are invalid" );
if (real->segment->char_count == 0 )
g_error ("iterator check failed: segment is not indexable." );
if (real->line_char_offset >= 0 && real->segment_char_offset < 0 )
g_error ("segment char offset is not properly up-to-date" );
if (real->line_byte_offset >= 0 && real->segment_byte_offset < 0 )
g_error ("segment byte offset is not properly up-to-date" );
if (real->segment_byte_offset >= 0 &&
real->segment_byte_offset >= real->segment->byte_count)
g_error ("segment byte offset is too large." );
if (real->segment_char_offset >= 0 &&
real->segment_char_offset >= real->segment->char_count)
g_error ("segment char offset is too large." );
}
if (real->line_byte_offset >= 0 )
{
_gtk_text_line_byte_locate (real->line, real->line_byte_offset,
&byte_segment, &byte_any_segment,
&seg_byte_offset, &line_byte_offset);
if (line_byte_offset != real->line_byte_offset)
g_error ("wrong byte offset was stored in iterator" );
if (segments_updated)
{
if (real->segment != byte_segment)
g_error ("wrong segment was stored in iterator" );
if (real->any_segment != byte_any_segment)
g_error ("wrong any_segment was stored in iterator" );
if (seg_byte_offset != real->segment_byte_offset)
g_error ("wrong segment byte offset was stored in iterator" );
if (byte_segment->type == >k_text_char_type)
{
const char *p;
p = byte_segment->body.chars + seg_byte_offset;
if (!gtk_text_byte_begins_utf8_char (p))
g_error ("broken iterator byte index pointed into the middle of a character" );
}
}
}
#ifdef _MSC_VER
else
seg_byte_offset = 0 ;
#endif
if (real->line_char_offset >= 0 )
{
_gtk_text_line_char_locate (real->line, real->line_char_offset,
&char_segment, &char_any_segment,
&seg_char_offset, &line_char_offset);
if (line_char_offset != real->line_char_offset)
g_error ("wrong char offset was stored in iterator" );
if (segments_updated)
{
if (real->segment != char_segment)
g_error ("wrong segment was stored in iterator" );
if (real->any_segment != char_any_segment)
g_error ("wrong any_segment was stored in iterator" );
if (seg_char_offset != real->segment_char_offset)
g_error ("wrong segment char offset was stored in iterator" );
if (char_segment->type == >k_text_char_type)
{
const char *p;
p = g_utf8_offset_to_pointer (char_segment->body.chars,
seg_char_offset);
/* hmm, not likely to happen eh */
if (!gtk_text_byte_begins_utf8_char (p))
g_error ("broken iterator char offset pointed into the middle of a character" );
}
}
}
#ifdef _MSC_VER
else
seg_char_offset = 0 ;
#endif
if (real->line_char_offset >= 0 && real->line_byte_offset >= 0 )
{
if (byte_segment != char_segment)
g_error ("char and byte offsets did not point to the same segment" );
if (byte_any_segment != char_any_segment)
g_error ("char and byte offsets did not point to the same any segment" );
/* Make sure the segment offsets are equivalent, if it's a char
segment. */
if (char_segment->type == >k_text_char_type)
{
int byte_offset = 0 ;
int char_offset = 0 ;
while (char_offset < seg_char_offset)
{
const char * start = char_segment->body.chars + byte_offset;
byte_offset += g_utf8_next_char (start) - start;
char_offset += 1 ;
}
if (byte_offset != seg_byte_offset)
g_error ("byte offset did not correspond to char offset" );
char_offset =
g_utf8_strlen (char_segment->body.chars, seg_byte_offset);
if (char_offset != seg_char_offset)
g_error ("char offset did not correspond to byte offset" );
if (!gtk_text_byte_begins_utf8_char (char_segment->body.chars + seg_byte_offset))
g_error ("byte index for iterator does not index the start of a character" );
}
}
if (real->cached_line_number >= 0 )
{
int should_be;
should_be = _gtk_text_line_get_number (real->line);
if (real->cached_line_number != should_be)
g_error ("wrong line number was cached" );
}
if (real->cached_char_index >= 0 )
{
if (real->line_char_offset >= 0 ) /* only way we can check it
efficiently , not a real
invariant. */
{
int char_index;
char_index = _gtk_text_line_char_index (real->line);
char_index += real->line_char_offset;
if (real->cached_char_index != char_index)
g_error ("wrong char index was cached" );
}
}
if (_gtk_text_line_is_last (real->line, real->tree))
g_error ("Iterator was on last line (past the end iterator)" );
}
Messung V0.5 in Prozent C=98 H=95 G=96
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.224Bemerkung:
(vorverarbeitet am 2026-07-03)
¤
*Bot Zugriff
2026-07-09