Commit 1e3b62fd authored by Havoc Pennington's avatar Havoc Pennington Committed by Havoc Pennington

Get rid of the newline-that-could-not-be-deleted; buffers may now be

2001-09-24  Havoc Pennington  <hp@redhat.com>

	* gtk/gtktextiter.c, gtk/gtktextbuffer.c, gtk/gtktextbtree.c,
	gtktextlayout.c:
	Get rid of the newline-that-could-not-be-deleted; buffers may
	now be zero-length. Much easier to fix than expected, once
	I figured out the right way to do it. However, there are
	various subtle bugs introduced by this that will have to get
	sorted out. Please use bugzilla.
parent 7837ddcd
2001-09-24 Havoc Pennington <hp@redhat.com>
* gtk/gtktextiter.c, gtk/gtktextbuffer.c, gtk/gtktextbtree.c,
gtktextlayout.c:
Get rid of the newline-that-could-not-be-deleted; buffers may
now be zero-length. Much easier to fix than expected, once
I figured out the right way to do it. However, there are
various subtle bugs introduced by this that will have to get
sorted out. Please use bugzilla.
Mon Sep 24 15:09:08 2001 Owen Taylor <otaylor@redhat.com>
* gtk/gtkwindow.c (gtk_window_move_resize): Don't wait for a
......
2001-09-24 Havoc Pennington <hp@redhat.com>
* gtk/gtktextiter.c, gtk/gtktextbuffer.c, gtk/gtktextbtree.c,
gtktextlayout.c:
Get rid of the newline-that-could-not-be-deleted; buffers may
now be zero-length. Much easier to fix than expected, once
I figured out the right way to do it. However, there are
various subtle bugs introduced by this that will have to get
sorted out. Please use bugzilla.
Mon Sep 24 15:09:08 2001 Owen Taylor <otaylor@redhat.com>
* gtk/gtkwindow.c (gtk_window_move_resize): Don't wait for a
......
2001-09-24 Havoc Pennington <hp@redhat.com>
* gtk/gtktextiter.c, gtk/gtktextbuffer.c, gtk/gtktextbtree.c,
gtktextlayout.c:
Get rid of the newline-that-could-not-be-deleted; buffers may
now be zero-length. Much easier to fix than expected, once
I figured out the right way to do it. However, there are
various subtle bugs introduced by this that will have to get
sorted out. Please use bugzilla.
Mon Sep 24 15:09:08 2001 Owen Taylor <otaylor@redhat.com>
* gtk/gtkwindow.c (gtk_window_move_resize): Don't wait for a
......
2001-09-24 Havoc Pennington <hp@redhat.com>
* gtk/gtktextiter.c, gtk/gtktextbuffer.c, gtk/gtktextbtree.c,
gtktextlayout.c:
Get rid of the newline-that-could-not-be-deleted; buffers may
now be zero-length. Much easier to fix than expected, once
I figured out the right way to do it. However, there are
various subtle bugs introduced by this that will have to get
sorted out. Please use bugzilla.
Mon Sep 24 15:09:08 2001 Owen Taylor <otaylor@redhat.com>
* gtk/gtkwindow.c (gtk_window_move_resize): Don't wait for a
......
2001-09-24 Havoc Pennington <hp@redhat.com>
* gtk/gtktextiter.c, gtk/gtktextbuffer.c, gtk/gtktextbtree.c,
gtktextlayout.c:
Get rid of the newline-that-could-not-be-deleted; buffers may
now be zero-length. Much easier to fix than expected, once
I figured out the right way to do it. However, there are
various subtle bugs introduced by this that will have to get
sorted out. Please use bugzilla.
Mon Sep 24 15:09:08 2001 Owen Taylor <otaylor@redhat.com>
* gtk/gtkwindow.c (gtk_window_move_resize): Don't wait for a
......
2001-09-24 Havoc Pennington <hp@redhat.com>
* gtk/gtktextiter.c, gtk/gtktextbuffer.c, gtk/gtktextbtree.c,
gtktextlayout.c:
Get rid of the newline-that-could-not-be-deleted; buffers may
now be zero-length. Much easier to fix than expected, once
I figured out the right way to do it. However, there are
various subtle bugs introduced by this that will have to get
sorted out. Please use bugzilla.
Mon Sep 24 15:09:08 2001 Owen Taylor <otaylor@redhat.com>
* gtk/gtkwindow.c (gtk_window_move_resize): Don't wait for a
......
2001-09-24 Havoc Pennington <hp@redhat.com>
* gtk/gtktextiter.c, gtk/gtktextbuffer.c, gtk/gtktextbtree.c,
gtktextlayout.c:
Get rid of the newline-that-could-not-be-deleted; buffers may
now be zero-length. Much easier to fix than expected, once
I figured out the right way to do it. However, there are
various subtle bugs introduced by this that will have to get
sorted out. Please use bugzilla.
Mon Sep 24 15:09:08 2001 Owen Taylor <otaylor@redhat.com>
* gtk/gtkwindow.c (gtk_window_move_resize): Don't wait for a
......
......@@ -102,6 +102,14 @@ between the old and new positions).
</footnote>
</para>
<para>
Text buffers always contain at least one line, but may be empty (that is,
buffers can contain zero characters). The last line in the text buffer never
ends in a line separator (such as newline); the other lines in the buffer always
end in a line separator. Line separators count as characters when computing
character counts and character offsets.
</para>
</refsect1>
......
......@@ -96,9 +96,9 @@ Creates a new #GtkMenu.
Adds a new #GtkMenuItem to the end of the menu's item list.
</para>
<!-- # Unused Parameters # -->
@menu: a #GtkMenu.
@child: The #GtkMenuItem to add.
<!-- # Unused Parameters # -->
@m:
@c:
......@@ -108,9 +108,9 @@ Adds a new #GtkMenuItem to the end of the menu's item list.
Adds a new #GtkMenuItem to the beginning of the menu's item list.
</para>
<!-- # Unused Parameters # -->
@menu: a #GtkMenu.
@child: The #GtkMenuItem to add.
<!-- # Unused Parameters # -->
@menu_child:
@m:
@c:
......@@ -122,10 +122,10 @@ Adds a new #GtkMenuItem to the menu's item list at the position
indicated by @position.
</para>
<!-- # Unused Parameters # -->
@menu: a #GtkMenu.
@child: The #GtkMenuItem to add.
@pos:
<!-- # Unused Parameters # -->
@position: The position in the item list where @child is added.
Positions are numbered from 0 to n-1.
......
......@@ -185,22 +185,32 @@ struct _GtkTextBTree {
guint tag_changed_handler;
guint tag_removed_handler;
/* Incremented when a segment with a byte size > 0
is added to or removed from the tree (i.e. the
length of a line may have changed, and lines may
have been added or removed). This invalidates
all outstanding iterators.
*/
* is added to or removed from the tree (i.e. the
* length of a line may have changed, and lines may
* have been added or removed). This invalidates
* all outstanding iterators.
*/
guint chars_changed_stamp;
/* Incremented when any segments are added or deleted;
this makes outstanding iterators recalculate their
pointed-to segment and segment offset.
*/
* this makes outstanding iterators recalculate their
* pointed-to segment and segment offset.
*/
guint segments_changed_stamp;
GtkTextLine *end_iter_line;
/* Cache the last line in the buffer */
GtkTextLine *last_line;
guint last_line_stamp;
/* Cache the next-to-last line in the buffer,
* containing the end iterator
*/
GtkTextLine *end_iter_line;
GtkTextLineSegment *end_iter_segment;
int end_iter_segment_byte_index;
int end_iter_segment_char_offset;
guint end_iter_line_stamp;
guint end_iter_segment_stamp;
GHashTable *child_anchor_table;
};
......@@ -409,9 +419,15 @@ _gtk_text_btree_new (GtkTextTagTable *table,
tree->chars_changed_stamp = g_random_int ();
tree->segments_changed_stamp = g_random_int ();
tree->last_line_stamp = tree->chars_changed_stamp - 1;
tree->last_line = NULL;
tree->end_iter_line_stamp = tree->chars_changed_stamp - 1;
tree->end_iter_segment_stamp = tree->segments_changed_stamp - 1;
tree->end_iter_line = NULL;
tree->end_iter_segment_byte_index = 0;
tree->end_iter_segment_char_offset = 0;
g_object_ref (G_OBJECT (tree->table));
tree->tag_changed_handler = g_signal_connect (G_OBJECT (tree->table),
......@@ -570,6 +586,7 @@ _gtk_text_btree_delete (GtkTextIter *start,
_gtk_text_btree_check (tree);
{
/* FIXME this code should no longer be required */
/*
* The code below is ugly, but it's needed to make sure there
* is always a dummy empty line at the end of the text. If the
......@@ -1903,11 +1920,20 @@ _gtk_text_btree_get_line (GtkTextBTree *tree,
return line;
}
GtkTextLine*
_gtk_text_btree_get_end_iter_line (GtkTextBTree *tree)
{
return
_gtk_text_btree_get_line (tree,
_gtk_text_btree_line_count (tree) - 1,
NULL);
}
GtkTextLine*
_gtk_text_btree_get_line_at_char (GtkTextBTree *tree,
gint char_index,
gint *line_start_index,
gint *real_char_index)
gint char_index,
gint *line_start_index,
gint *real_char_index)
{
GtkTextBTreeNode *node;
GtkTextLine *line;
......@@ -1918,10 +1944,13 @@ _gtk_text_btree_get_line_at_char (GtkTextBTree *tree,
node = tree->root_node;
/* Clamp to valid indexes (-1 is magic for "highest index") */
if (char_index < 0 || char_index >= node->num_chars)
/* Clamp to valid indexes (-1 is magic for "highest index"),
* node->num_chars includes the two newlines that aren't really
* in the buffer.
*/
if (char_index < 0 || char_index >= (node->num_chars - 1))
{
char_index = node->num_chars - 1;
char_index = node->num_chars - 2;
}
*real_char_index = char_index;
......@@ -2242,8 +2271,10 @@ _gtk_text_btree_line_count (GtkTextBTree *tree)
gint
_gtk_text_btree_char_count (GtkTextBTree *tree)
{
/* Exclude newline in bogus last line */
return tree->root_node->num_chars - 1;
/* Exclude newline in bogus last line and the
* one in the last line that is after the end iterator
*/
return tree->root_node->num_chars - 2;
}
#define LOTSA_TAGS 1000
......@@ -3137,6 +3168,88 @@ _gtk_text_line_is_last (GtkTextLine *line,
return line == get_last_line (tree);
}
static void
ensure_end_iter_line (GtkTextBTree *tree)
{
if (tree->end_iter_line_stamp != tree->chars_changed_stamp)
{
int n_lines;
int real_line;
/* n_lines is without the magic line at the end */
n_lines = _gtk_text_btree_line_count (tree);
g_assert (n_lines >= 1);
tree->end_iter_line = _gtk_text_btree_get_line (tree, n_lines - 1, &real_line);
tree->end_iter_line_stamp = tree->chars_changed_stamp;
}
}
static void
ensure_end_iter_segment (GtkTextBTree *tree)
{
if (tree->end_iter_segment_stamp != tree->segments_changed_stamp)
{
GtkTextLineSegment *seg;
GtkTextLineSegment *last_with_chars;
ensure_end_iter_line (tree);
last_with_chars = NULL;
seg = tree->end_iter_line->segments;
while (seg != NULL)
{
if (seg->char_count > 0)
last_with_chars = seg;
seg = seg->next;
}
tree->end_iter_segment = last_with_chars;
/* We know the last char in the last line is '\n' */
tree->end_iter_segment_byte_index = last_with_chars->byte_count - 1;
tree->end_iter_segment_char_offset = last_with_chars->char_count - 1;
tree->end_iter_segment_stamp = tree->segments_changed_stamp;
}
}
gboolean
_gtk_text_line_contains_end_iter (GtkTextLine *line,
GtkTextBTree *tree)
{
ensure_end_iter_line (tree);
return line == tree->end_iter_line;
}
gboolean
_gtk_text_btree_is_end (GtkTextBTree *tree,
GtkTextLine *line,
GtkTextLineSegment *seg,
int byte_index,
int char_offset)
{
g_return_val_if_fail (byte_index >= 0 || char_offset >= 0, FALSE);
/* Do this first to avoid walking segments in most cases */
if (!_gtk_text_line_contains_end_iter (line, tree))
return FALSE;
ensure_end_iter_segment (tree);
if (seg != tree->end_iter_segment)
return FALSE;
if (byte_index >= 0)
return byte_index == tree->end_iter_segment_byte_index;
else
return char_offset == tree->end_iter_segment_char_offset;
}
GtkTextLine*
_gtk_text_line_next (GtkTextLine *line)
{
......@@ -3172,6 +3285,23 @@ _gtk_text_line_next (GtkTextLine *line)
}
}
GtkTextLine*
_gtk_text_line_next_excluding_last (GtkTextLine *line)
{
GtkTextLine *next;
next = _gtk_text_line_next (line);
/* If we were on the end iter line, we can't go to
* the last line
*/
if (next && next->next == NULL && /* these checks are optimization only */
_gtk_text_line_next (next) == NULL)
return NULL;
return next;
}
GtkTextLine*
_gtk_text_line_previous (GtkTextLine *line)
{
......@@ -3950,9 +4080,9 @@ node_compare (GtkTextBTreeNode *lhs,
/* remember that tag == NULL means "any tag" */
GtkTextLine*
_gtk_text_line_next_could_contain_tag (GtkTextLine *line,
GtkTextBTree *tree,
GtkTextTag *tag)
_gtk_text_line_next_could_contain_tag (GtkTextLine *line,
GtkTextBTree *tree,
GtkTextTag *tag)
{
GtkTextBTreeNode *node;
GtkTextTagInfo *info;
......@@ -3968,17 +4098,17 @@ _gtk_text_line_next_could_contain_tag (GtkTextLine *line,
/* Right now we can only offer linear-search if the user wants
* to know about any tag toggle at all.
*/
return _gtk_text_line_next (line);
return _gtk_text_line_next_excluding_last (line);
}
/* Our tag summaries only have node precision, not line
precision. This means that if any line under a node could contain a
tag, then any of the others could also contain a tag.
In the future we could have some mechanism to keep track of how
many toggles we've found under a node so far, since we have a
count of toggles under the node. But for now I'm going with KISS.
*/
* precision. This means that if any line under a node could contain a
* tag, then any of the others could also contain a tag.
*
* In the future we could have some mechanism to keep track of how
* many toggles we've found under a node so far, since we have a
* count of toggles under the node. But for now I'm going with KISS.
*/
/* return same-node line, if any. */
if (line->next)
......@@ -4330,7 +4460,7 @@ summary_list_destroy (Summary *summary)
static GtkTextLine*
get_last_line (GtkTextBTree *tree)
{
if (tree->end_iter_line_stamp != tree->chars_changed_stamp)
if (tree->last_line_stamp != tree->chars_changed_stamp)
{
gint n_lines;
GtkTextLine *line;
......@@ -4342,11 +4472,11 @@ get_last_line (GtkTextBTree *tree)
line = _gtk_text_btree_get_line (tree, n_lines, &real_line);
tree->end_iter_line_stamp = tree->chars_changed_stamp;
tree->end_iter_line = line;
tree->last_line_stamp = tree->chars_changed_stamp;
tree->last_line = line;
}
return tree->end_iter_line;
return tree->last_line;
}
/*
......
......@@ -23,6 +23,11 @@ guint _gtk_text_btree_get_chars_changed_stamp (GtkTextBTree *tree);
guint _gtk_text_btree_get_segments_changed_stamp (GtkTextBTree *tree);
void _gtk_text_btree_segments_changed (GtkTextBTree *tree);
gboolean _gtk_text_btree_is_end (GtkTextBTree *tree,
GtkTextLine *line,
GtkTextLineSegment *seg,
int byte_index,
int char_offset);
/* Indexable segment mutation */
......@@ -82,6 +87,7 @@ void _gtk_text_btree_tag (const GtkTextIter *start,
GtkTextLine * _gtk_text_btree_get_line (GtkTextBTree *tree,
gint line_number,
gint *real_line_number);
GtkTextLine * _gtk_text_btree_get_end_iter_line (GtkTextBTree *tree);
GtkTextLine * _gtk_text_btree_get_line_at_char (GtkTextBTree *tree,
gint char_index,
gint *line_start_index,
......@@ -205,9 +211,12 @@ gboolean _gtk_text_line_byte_has_tag (GtkTextLine
GtkTextBTree *tree,
gint byte_in_line,
GtkTextTag *tag);
gboolean _gtk_text_line_is_last (GtkTextLine *line,
GtkTextBTree *tree);
gboolean _gtk_text_line_is_last (GtkTextLine *line,
GtkTextBTree *tree);
gboolean _gtk_text_line_contains_end_iter (GtkTextLine *line,
GtkTextBTree *tree);
GtkTextLine * _gtk_text_line_next (GtkTextLine *line);
GtkTextLine * _gtk_text_line_next_excluding_last (GtkTextLine *line);
GtkTextLine * _gtk_text_line_previous (GtkTextLine *line);
void _gtk_text_line_add_data (GtkTextLine *line,
GtkTextLineData *data);
......
......@@ -407,11 +407,8 @@ gtk_text_buffer_get_tag_table (GtkTextBuffer *buffer)
* @text: UTF-8 text to insert
* @len: length of @text in bytes
*
* Deletes current contents of @buffer, and inserts @text instead. If
* @text doesn't end with a newline, a newline is added;
* #GtkTextBuffer contents must always end with a newline. If @text
* ends with a newline, the new buffer contents will be exactly
* @text. If @len is -1, @text must be nul-terminated.
* Deletes current contents of @buffer, and inserts @text instead. If
* @len is -1, @text must be nul-terminated. @text must be valid UTF-8.
**/
void
gtk_text_buffer_set_text (GtkTextBuffer *buffer,
......@@ -426,12 +423,6 @@ gtk_text_buffer_set_text (GtkTextBuffer *buffer,
if (len < 0)
len = strlen (text);
/* Chop newline, since the buffer will already have one
* in it.
*/
if (len > 0 && text[len-1] == '\n')
len -= 1;
gtk_text_buffer_get_bounds (buffer, &start, &end);
gtk_text_buffer_delete (buffer, &start, &end);
......@@ -1135,14 +1126,6 @@ gtk_text_buffer_emit_delete (GtkTextBuffer *buffer,
gtk_text_iter_order (start, end);
/* Somewhat annoyingly, if you try to delete the final newline
* the BTree will put it back; which means you can't deduce the
* final contents of the buffer purely by monitoring insert/delete
* signals on the buffer. But if you delete the final newline, any
* tags on the newline will go away, oddly. See comment in
* gtktextbtree.c. This is all sort of annoying, but really hard
* to fix.
*/
g_signal_emit (G_OBJECT (buffer),
signals[DELETE_RANGE],
0,
......@@ -1163,11 +1146,6 @@ gtk_text_buffer_emit_delete (GtkTextBuffer *buffer,
* calling this function; however, the @start and @end will be
* re-initialized to point to the location where text was deleted.
*
* Note that the final newline in the buffer may not be deleted; a
* #GtkTextBuffer always contains at least one newline. You can
* safely include the final newline in the range [@start,@end) but it
* won't be affected by the deletion.
*
**/
void
gtk_text_buffer_delete (GtkTextBuffer *buffer,
......@@ -1529,14 +1507,15 @@ gtk_text_buffer_mark_set (GtkTextBuffer *buffer,
GtkTextMark *mark)
{
/* IMO this should NOT work like insert_text and delete_range,
where the real action happens in the default handler.
The reason is that the default handler would be _required_,
i.e. the whole widget would start breaking and segfaulting
if the default handler didn't get run. So you can't really
override the default handler or stop the emission; that is,
this signal is purely for notification, and not to allow users
to modify the default behavior. */
* where the real action happens in the default handler.
*
* The reason is that the default handler would be _required_,
* i.e. the whole widget would start breaking and segfaulting if the
* default handler didn't get run. So you can't really override the
* default handler or stop the emission; that is, this signal is
* purely for notification, and not to allow users to modify the
* default behavior.
*/
g_object_ref (G_OBJECT (mark));
......@@ -2701,8 +2680,6 @@ clipboard_get_contents_cb (GtkClipboard *clipboard,
GtkTextIter start, end;
gtk_text_buffer_get_bounds (contents, &start, &end);
/* strip off the trailing newline, it isn't part of the text that was cut */
gtk_text_iter_backward_char (&end);
str = gtk_text_iter_get_visible_text (&start, &end);
gtk_selection_data_set_text (selection_data, str);
......@@ -3466,8 +3443,16 @@ _gtk_text_buffer_get_line_log_attrs (GtkTextBuffer *buffer,
g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
g_return_val_if_fail (anywhere_in_line != NULL, NULL);
g_return_val_if_fail (!gtk_text_iter_is_end (anywhere_in_line), NULL);
/* special-case for empty last line in buffer */
if (gtk_text_iter_is_end (anywhere_in_line) &&
gtk_text_iter_get_line_offset (anywhere_in_line) == 0)
{
if (char_len)
*char_len = 0;
return NULL;
}
/* FIXME we also need to recompute log attrs if the language tag at
* the start of a paragraph changes
*/
......
......@@ -1532,7 +1532,19 @@ gtk_text_iter_is_end (const GtkTextIter *iter)
check_invariants (iter);
return _gtk_text_line_is_last (real->line, real->tree);
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);
}
/**
......@@ -1701,18 +1713,23 @@ gtk_text_iter_get_attributes (const GtkTextIter *iter,
/* 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)
{
GtkTextLine *new_line;
new_line = _gtk_text_line_next (real->line);
g_assert (new_line != real->line);
if (new_line != NULL)
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;
......@@ -1731,24 +1748,11 @@ forward_line_leaving_caches_unmodified (GtkTextRealIter *real)
}
else
{
/* There is no way to move forward; we were already
at the "end" index. (the end index is the last
line pointer, segment_byte_offset of 0) */
g_assert (real->line_char_offset == 0 ||
real->line_byte_offset == 0);
/* The only indexable segment allowed on the bogus
line at the end is a single char segment containing
a newline. */
if (real->segments_changed_stamp ==
_gtk_text_btree_get_segments_changed_stamp (real->tree))
{
g_assert (real->segment->type == &gtk_text_char_type);
g_assert (real->segment->char_count == 1);
}
/* We leave real->line as-is */
/* 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;
}
}
......@@ -1950,6 +1954,8 @@ _gtk_text_iter_forward_indexable_segment (GtkTextIter *iter)
{
/* End of buffer */