<commit_before>/*
 * Copyright (C) 2002,2009,2010 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.1 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Red Hat Author(s): <NAME>, <NAME>
 */

#include <config.h>

#include "debug.h"
#include "ring.h"

#include <string.h>

/*
 * VteRing: A buffer ring
 */

#ifdef VTE_DEBUG
static void
_vte_ring_validate (VteRing * ring)
{
	g_assert(ring != NULL);
	_vte_debug_print(VTE_DEBUG_RING,
			" Delta = %lu, Length = %lu, Next = %lu, Max = %lu, Writable = %lu.\n",
			ring->start, ring->end - ring->start, ring->end,
			ring->max, ring->end - ring->writable);

	g_assert (ring->start <= ring->writable);
	g_assert (ring->writable <= ring->end);

	g_assert (ring->end - ring->start <= ring->max);
	g_assert (ring->end - ring->writable <= ring->mask);
}
#else
#define _vte_ring_validate(ring) G_STMT_START {} G_STMT_END
#endif


void
_vte_ring_init (VteRing *ring, gulong max_rows, gboolean has_streams)
{
	_vte_debug_print(VTE_DEBUG_RING, "New ring %p.\n", ring);

	memset (ring, 0, sizeof (*ring));

	ring->max = MAX (max_rows, 3);

	ring->mask = 31;
	ring->array = (VteRowData *) g_malloc0 (sizeof (ring->array[0]) * (ring->mask + 1));

	ring->has_streams = has_streams;
	if (has_streams) {
		ring->attr_stream = _vte_file_stream_new ();
		ring->text_stream = _vte_file_stream_new ();
		ring->row_stream = _vte_file_stream_new ();
	} else {
		ring->attr_stream = ring->text_stream = ring->row_stream = NULL;
	}

	ring->last_attr_text_start_offset = 0;
	ring->last_attr.i = basic_cell.i.attr;
	ring->utf8_buffer = g_string_sized_new (128);

	_vte_row_data_init (&ring->cached_row);
	ring->cached_row_num = (gulong) -1;

        ring->visible_rows = 0;

	_vte_ring_validate(ring);
}

void
_vte_ring_fini (VteRing *ring)
{
	gulong i;

	for (i = 0; i <= ring->mask; i++)
		_vte_row_data_fini (&ring->array[i]);

	g_free (ring->array);

	if (ring->has_streams) {
		g_object_unref (ring->attr_stream);
		g_object_unref (ring->text_stream);
		g_object_unref (ring->row_stream);
	}

	g_string_free (ring->utf8_buffer, TRUE);

	_vte_row_data_fini (&ring->cached_row);
}

typedef struct _VteRowRecord {
	gsize text_start_offset;  /* offset where text of this row begins */
	gsize attr_start_offset;  /* offset of the first character's attributes */
	int soft_wrapped: 1;      /* end of line is not '\n' */
	int is_ascii: 1;          /* for rewrapping speedup: guarantees that line contains 32..126 bytes only. Can be 0 even when ascii only. */
} VteRowRecord;

/* Represents a cell position, see ../doc/rewrap.txt */
typedef struct _VteCellTextOffset {
	gsize text_offset;    /* byte offset in text_stream (or perhaps beyond) */
	gint fragment_cells;  /* extra number of cells to walk within a multicell character */
	gint eol_cells;       /* -1 if over a character, >=0 if at EOL or beyond */
} VteCellTextOffset;

static gboolean
_vte_ring_read_row_record (VteRing *ring, VteRowRecord *record, gulong position)
{
	return _vte_stream_read (ring->row_stream, position * sizeof (*record), (char *) record, sizeof (*record));
}

static void
_vte_ring_append_row_record (VteRing *ring, const VteRowRecord *record, gulong position)
{
	_vte_stream_append (ring->row_stream, (const char *) record, sizeof (*record));
}

static void
_vte_ring_freeze_row (VteRing *ring, gulong position, const VteRowData *row)
{
	VteRowRecord record;
	VteCell *cell;
	GString *buffer = ring->utf8_buffer;
	int i;

	_vte_debug_print (VTE_DEBUG_RING, "Freezing row %lu.\n", position);

        g_assert(ring->has_streams);

	memset(&record, 0, sizeof (record));
	record.text_start_offset = _vte_stream_head (ring->text_stream);
	record.attr_start_offset = _vte_stream_head (ring->attr_stream);
	record.is_ascii = 1;

	g_string_set_size (buffer, 0);
	for (i = 0, cell = row->cells; i < row->len; i++, cell++) {
		VteIntCellAttr attr;
		int num_chars;

		/* Attr storage:
		 *
		 * 1. We don't store attrs for fragments.  They can be
		 * reconstructed using the columns of their start cell.
		 *
		 * 2. We store one attr per vteunistr character starting
		 * from the second character, with columns=0.
		 *
		 * That's enough to reconstruct the attrs, and to store
		 * the text in real UTF-8.
		 */
		attr.s = cell->attr;
		if (G_LIKELY (!attr.s.fragment)) {
			VteCellAttrChange attr_change;

			if (ring->last_attr.i != attr.i) {
				ring->last_attr_text_start_offset = record.text_start_offset + buffer->len;
				memset(&attr_change, 0, sizeof (attr_change));
				attr_change.text_end_offset = ring->last_attr_text_start_offset;
				attr_change.attr = ring->last_attr;
				_vte_stream_append (ring->attr_stream, (const char *) &attr_change, sizeof (attr_change));
				if (!buffer->len)
					/* This row doesn't use last_attr, adjust */
					record.attr_start_offset += sizeof (attr_change);
				ring->last_attr = attr;
			}

			num_chars = _vte_unistr_strlen (cell->c);
			if (num_chars > 1) {
				attr.s.columns = 0;
				ring->last_attr_text_start_offset = record.text_start_offset + buffer->len
								  + g_unichar_to_utf8 (_vte_unistr_get_base (cell->c), NULL);
				memset(&attr_change, 0, sizeof (attr_change));
				attr_change.text_end_offset = ring->last_attr_text_start_offset;
				attr_change.attr = ring->last_attr;
				_vte_stream_append (ring->attr_stream, (const char *) &attr_change, sizeof (attr_change));
				ring->last_attr = attr;
			}

			if (cell->c < 32 || cell->c > 126) record.is_ascii = 0;
			_vte_unistr_append_to_string (cell->c, buffer);
		}
	}
	if (!row->attr.soft_wrapped)
		g_string_append_c (buffer, '\n');
	record.soft_wrapped = row->attr.soft_wrapped;

	_vte_stream_append (ring->text_stream, buffer->str, buffer->len);
	_vte_ring_append_row_record (ring, &record, position);
}

static void
_vte_ring_thaw_row (VteRing *ring, gulong position, VteRowData *row, gboolean do_truncate)
{
	VteRowRecord records[2], record;
	VteIntCellAttr attr;
	VteCellAttrChange attr_change;
	VteCell cell;
	const char *p, *q, *end;
	GString *buffer = ring->utf8_buffer;

	_vte_debug_print (VTE_DEBUG_RING, "Thawing row %lu.\n", position);

        g_assert(ring->has_streams);

	_vte_row_data_clear (row);

	attr_change.text_end_offset = 0;

	if (!_vte_ring_read_row_record (ring, &records[0], position))
		return;
	if ((position + 1) * sizeof (records[0]) < _vte_stream_head (ring->row_stream)) {
		if (!_vte_ring_read_row_record (ring, &records[1], position + 1))
			return;
	} else
		records[1].text_start_offset = _vte_stream_head (ring->text_stream);

	g_string_set_size (buffer, records[1].text_start_offset - records[0].text_start_offset);
	if (!_vte_stream_read (ring->text_stream, records[0].text_start_offset, buffer->str, buffer->len))
		return;

	record = records[0];

	if (G_LIKELY (buffer->len && buffer->str[buffer->len - 1] == '\n'))
		buffer->len--;
	else
		row->attr.soft_wrapped = TRUE;

	p = buffer->str;
	end = p + buffer->len;
	while (p < end) {

		if (record.text_start_offset >= ring->last_attr_text_start_offset) {
			attr = ring->last_attr;
		} else {
			if (record.text_start_offset >= attr_change.text_end_offset) {
				if (!_vte_stream_read (ring->attr_stream, record.attr_start_offset, (char *) &attr_change, sizeof (attr_change)))
					return;
				record.attr_start_offset += sizeof (attr_change);
			}
			attr = attr_change.attr;
		}

		cell.attr = attr.s;
		cell.c = g_utf8_get_char (p);

		q = g_utf8_next_char (p);
		record.text_start_offset += q - p;
		p = q;

		if (G_UNLIKELY (cell.attr.columns == 0)) {
			if (G_LIKELY (row->len)) {
				/* Combine it */
				row->cells[row->len - 1].c = _vte_unistr_append_unichar (row->cells[row->len - 1].c, cell.c);
			} else {
				cell.attr.columns = 1;
				_vte_row_data_append (row, &cell);
			}
		} else {
			_vte_row_data_append (row, &cell);
			if (cell.attr.columns > 1) {
				/* Add the fragments */
				int i, columns = cell.attr.columns;
				cell.attr.fragment = 1;
				cell.attr.columns = 1;
				for (i = 1; i < columns; i++)
					_vte_row_data_append (row, &cell);
			}
		}
	}

	if (do_truncate) {
		gsize attr_stream_truncate_at = records[0].attr_start_offset;
		_vte_debug_print (VTE_DEBUG_RING, "Truncating\n");
		if (records[0].text_start_offset <= ring->last_attr_text_start_offset) {
			/* Check the previous attr record. If its text ends where truncating, this attr record also needs to be removed. */
			if (_vte_stream_read (ring->attr_stream, attr_stream_truncate_at - sizeof (attr_change), (char *) &attr_change, sizeof (attr_change))) {
				if (records[0].text_start_offset == attr_change.text_end_offset) {
					_vte_debug_print (VTE_DEBUG_RING, "... at attribute change\n");
					attr_stream_truncate_at -= sizeof (attr_change);
				}
			}
			/* Reconstruct last_attr from the first record of attr_stream that we cut off,
			   last_attr_text_start_offset from the last record that we keep. */
			if (_vte_stream_read (ring->attr_stream, attr_stream_truncate_at, (char *) &attr_change, sizeof (attr_change))) {
				ring->last_attr = attr_change.attr;
				if (_vte_stream_read (ring->attr_stream, attr_stream_truncate_at - sizeof (attr_change), (char *) &attr_change, sizeof (attr_change))) {
					ring->last_attr_text_start_offset = attr_change.text_end_offset;
				} else {
					ring->last_attr_text_start_offset = 0;
				}
			} else {
				ring->last_attr_text_start_offset = 0;
				ring->last_attr.i = basic_cell.i.attr;
			}
		}
		_vte_stream_truncate (ring->row_stream, position * sizeof (record));
		_vte_stream_truncate (ring->attr_stream, attr_stream_truncate_at);
		_vte_stream_truncate (ring->text_stream, records[0].text_start_offset);
	}
}

static void
_vte_ring_reset_streams (VteRing *ring, gulong position)
{
	_vte_debug_print (VTE_DEBUG_RING, "Reseting streams to %lu.\n", position);

	if (ring->has_streams) {
		_vte_stream_reset (ring->row_stream, position * sizeof (VteRowRecord));
                _vte_stream_reset (ring->text_stream, _vte_stream_head (ring->text_stream));
                _vte_stream_reset (ring->attr_stream, _vte_stream_head (ring->attr_stream));
	}

	ring->last_attr_text_start_offset = 0;
	ring->last_attr.i = basic_cell.i.attr;
}

long
_vte_ring_reset (VteRing *ring)
{
        _vte_debug_print (VTE_DEBUG_RING, "Reseting the ring at %lu.\n", ring->end);

        _vte_ring_reset_streams (ring, ring->end);
        ring->start = ring->writable = ring->end;
        ring->cached_row_num = (gulong) -1;

        return ring->end;
}

static inline VteRowData *
_vte_ring_writable_index (VteRing *ring, gulong position)
{
	return &ring->array[position & ring->mask];
}

const VteRowData *
_vte_ring_index (VteRing *ring, gulong position)
{
	if (G_LIKELY (position >= ring->writable))
		return _vte_ring_writable_index (ring, position);

	if (ring->cached_row_num != position) {
		_vte_debug_print(VTE_DEBUG_RING, "Caching row %lu.\n", position);
		_vte_ring_thaw_row (ring, position, &ring->cached_row, FALSE);
		ring->cached_row_num = position;
	}

	return &ring->cached_row;
}

static void _vte_ring_ensure_writable (VteRing *ring, gulong position);
static void _vte_ring_ensure_writable_room (VteRing *ring);

VteRowData *
_vte_ring_index_writable (VteRing *ring, gulong position)
{
	_vte_ring_ensure_writable (ring, position);
	return _vte_ring_writable_index (ring, position);
}

static void
_vte_ring_freeze_one_row (VteRing *ring)
{
	VteRowData *row;

	if (G_UNLIKELY (ring->writable == ring->start))
		_vte_ring_reset_streams (ring, ring->writable);

	row = _vte_ring_writable_index (ring, ring->writable);
	_vte_ring_freeze_row (ring, ring->writable, row);

	ring->writable++;
}

static void
_vte_ring_thaw_one_row (VteRing *ring)
{
	VteRowData *row;

	g_assert (ring->start < ring->writable);

	_vte_ring_ensure_writable_room (ring);

	ring->writable--;

	if (ring->writable == ring->cached_row_num)
		ring->cached_row_num = (gulong) -1; /* Invalidate cached row */

	row = _vte_ring_writable_index (ring, ring->writable);

	_vte_ring_thaw_row (ring, ring->writable, row, TRUE);
}

static void
_vte_ring_discard_one_row (VteRing *ring)
{
	ring->start++;
	if (G_UNLIKELY (ring->start == ring->writable)) {
		_vte_ring_reset_streams (ring, ring->writable);
	} else if (ring->start < ring->writable) {
		VteRowRecord record;
		_vte_stream_advance_tail (ring->row_stream, ring->start * sizeof (record));
		if (G_LIKELY (_vte_ring_read_row_record (ring, &record, ring->start))) {
			_vte_stream_advance_tail (ring->text_stream, record.text_start_offset);
			_vte_stream_advance_tail (ring->attr_stream, record.attr_start_offset);
		}
	} else {
		ring->writable = ring->start;
	}
}

static void
_vte_ring_maybe_freeze_one_row (VteRing *ring)
{
        if (G_LIKELY (ring->mask >= ring->visible_rows && ring->writable + ring->mask + 1 == ring->end))
		_vte_ring_freeze_one_row (ring);
	else
		_vte_ring_ensure_writable_room (ring);
}

static void
_vte_ring_maybe_discard_one_row (VteRing *ring)
{
	if ((gulong) _vte_ring_length (ring) == ring->max)
		_vte_ring_discard_one_row (ring);
}

static void
_vte_ring_ensure_writable_room (VteRing *ring)
{
	gulong new_mask, old_mask, i, end;
	VteRowData *old_array, *new_array;;

        if (G_LIKELY (ring->mask >= ring->visible_rows && ring->writable + ring->mask + 1 > ring->end))
		return;

	old_mask = ring->mask;
	old_array = ring->array;

	do {
		ring->mask = (ring->mask << 1) + 1;
        } while (ring->mask < ring->visible_rows || ring->writable + ring->mask + 1 <= ring->end);

	_vte_debug_print(VTE_DEBUG_RING, "Enlarging writable array from %lu to %lu\n", old_mask, ring->mask);

	ring->array = (VteRowData *) g_malloc0 (sizeof (ring->array[0]) * (ring->mask + 1));

	new_mask = ring->mask;
	new_array = ring->array;

	end = ring->writable + old_mask + 1;
	for (i = ring->writable; i < end; i++)
		new_array[i & new_mask] = old_array[i & old_mask];

	g_free (old_array);
}

static void
_vte_ring_ensure_writable (VteRing *ring, gulong position)
{
	if (G_LIKELY (position >= ring->writable))
		return;

	_vte_debug_print(VTE_DEBUG_RING, "Ensure writable %lu.\n", position);

	while (position < ring->writable)
		_vte_ring_thaw_one_row (ring);
}

/**
 * _vte_ring_resize:
 * @ring: a #VteRing
 * @max_rows: new maximum numbers of rows in the ring
 *
 * Changes the number of lines the ring can contain.
 */
void
_vte_ring_resize (VteRing *ring, gulong max_rows)
{
	_vte_debug_print(VTE_DEBUG_RING, "Resizing to %lu.\n", max_rows);
	_vte_ring_validate(ring);

	/* Adjust the start of tail chunk now */
	if ((gulong) _vte_ring_length (ring) > max_rows) {
		ring->start = ring->end - max_rows;
		if (ring->start >= ring->writable) {
			_vte_ring_reset_streams (ring, ring->writable);
			ring->writable = ring->start;
		}
	}

	ring->max = max_rows;
}

void
_vte_ring_shrink (VteRing *ring, gulong max_len)
{
	if ((gulong) _vte_ring_length (ring) <= max_len)
		return;

	_vte_debug_print(VTE_DEBUG_RING, "Shrinking to %lu.\n", max_len);
	_vte_ring_validate(ring);

	if (ring->writable - ring->start <= max_len)
		ring->end = ring->start + max_len;
	else {
		while (ring->writable - ring->start > max_len) {
			_vte_ring_ensure_writable (ring, ring->writable - 1);
			ring->end = ring->writable;
		}
	}

	/* TODO May want to shrink down ring->array */

	_vte_ring_validate(ring);
}

/**
 * _vte_ring_insert_internal:
 * @ring: a #VteRing
 * @position: an index
 *
 * Inserts a new, empty, row into @ring at the @position'th offset.
 * The item at that position and any items after that are shifted down.
 *
 * Return: the newly added row.
 */
VteRowData *
_vte_ring_insert (VteRing *ring, gulong position)
{
	gulong i;
	VteRowData *row, tmp;

	_vte_debug_print(VTE_DEBUG_RING, "Inserting at position %lu.\n", position);
	_vte_ring_validate(ring);

	_vte_ring_maybe_discard_one_row (ring);

	_vte_ring_ensure_writable (ring, position);
	_vte_ring_ensure_writable_room (ring);

	g_assert (position >= ring->writable && position <= ring->end);

	tmp = *_vte_ring_writable_index (ring, ring->end);
	for (i = ring->end; i > position; i--)
		*_vte_ring_writable_index (ring, i) = *_vte_ring_writable_index (ring, i - 1);
	*_vte_ring_writable_index (ring, position) = tmp;

	row = _vte_ring_writable_index (ring, position);
	_vte_row_data_clear (row);
	ring->end++;

	_vte_ring_maybe_freeze_one_row (ring);

	_vte_ring_validate(ring);
	return row;
}

/**
 * _vte_ring_remove:
 * @ring: a #VteRing
 * @position: an index
 *
 * Removes the @position'th item from @ring.
 */
void
_vte_ring_remove (VteRing * ring, gulong position)
{
	gulong i;
	VteRowData tmp;

	_vte_debug_print(VTE_DEBUG_RING, "Removing item at position %lu.\n", position);
	_vte_ring_validate(ring);

	if (G_UNLIKELY (!_vte_ring_contains (ring, position)))
		return;

	_vte_ring_ensure_writable (ring, position);

	tmp = *_vte_ring_writable_index (ring, position);
	for (i = position; i < ring->end - 1; i++)
		*_vte_ring_writable_index (ring, i) = *_vte_ring_writable_index (ring, i + 1);
	*_vte_ring_writable_index (ring, ring->end - 1) = tmp;

	if (ring->end > ring->writable)
		ring->end--;

	_vte_ring_validate(ring);
}


/**
 * _vte_ring_append:
 * @ring: a #VteRing
 * @data: the new item
 *
 * Appends a new item to the ring.
 *
 * Return: the newly added row.
 */
VteRowData *
_vte_ring_append (VteRing * ring)
{
	return _vte_ring_insert (ring, _vte_ring_next (ring));
}


/**
 * _vte_ring_drop_scrollback:
 * @ring: a #VteRing
 * @position: drop contents up to this point, which must be in the writable region.
 *
 * Drop the scrollback (offscreen contents).
 *
 * TODOegmont: We wouldn't need the position argument after addressing 708213#c29.
 */
void
_vte_ring_drop_scrollback (VteRing * ring, gulong position)
{
        _vte_ring_ensure_writable (ring, position);

        ring->start = ring->writable = position;
        _vte_ring_reset_streams (ring, position);
}


/**
 * _vte_ring_set_visible_rows:
 * @ring: a #VteRing
 *
 * Set the number of visible rows.
 * It's required to be set correctly for the alternate screen so that it
 * never hits the streams. It's also required for clearing the scrollback.
 */
void
_vte_ring_set_visible_rows (VteRing *ring, gulong rows)
{
        ring->visible_rows = rows;
}


/* Convert a (row,col) into a VteCellTextOffset.
 * Requires the row to be frozen, or be outsize the range covered by the ring.
 */
static gboolean
_vte_frozen_row_column_to_text_offset (VteRing *ring,
				       gulong position,
				       gulong column,
				       VteCellTextOffset *offset)
{
	VteRowRecord records[2];
	VteCell *cell;
	GString *buffer = ring->utf8_buffer;
	const VteRowData *row;
	unsigned int i, num_chars, off;

	if (position >= ring->end) {
		offset->text_offset = _vte_stream_head (ring->text_stream) + position - ring->end;
		offset->fragment_cells = 0;
		offset->eol_cells = column;
		return TRUE;
	}

	if (G_UNLIKELY (position < ring->start)) {
		/* This happens when the marker (saved cursor position) is
		   scrolled off at the top of the scrollback buffer. */
		position = ring->start;
		column = 0;
		/* go on */
	}

	g_assert(position < ring->writable);
	if (!_vte_ring_read_row_record (ring, &records[0], position))
		return FALSE;
	if ((position + 1) * sizeof (records[0]) < _vte_stream_head (ring->row_stream)) {
		if (!_vte_ring_read_row_record (ring, &records[1], position + 1))
			return FALSE;
	} else
		records[1].text_start_offset = _vte_stream_head (ring->text_stream);

	g_string_set_size (buffer, records[1].text_start_offset - records[0].text_start_offset);
	if (!_vte_stream_read (ring->text_stream, records[0].text_start_offset, buffer->str, buffer->len))
		return FALSE;

	if (G_LIKELY (buffer->len && buffer->str[buffer->len - 1] == '\n'))
		buffer->len--;

	row = _vte_ring_index(ring, position);

	/* row and buffer now contain the same text, in different representation */

	/* count the number of characters up to the given column */
	offset->fragment_cells = 0;
	offset->eol_cells = -1;
	num_chars = 0;
	for (i = 0, cell = row->cells; i < row->len && i < column; i++, cell++) {
		if (G_LIKELY (!cell->attr.fragment)) {
			if (G_UNLIKELY (i + cell->attr.columns > column)) {
				offset->fragment_cells = column - i;
				break;
			}
			num_chars += _vte_unistr_strlen(cell->c);
		}
	}
	if (i >= row->len) {
		offset->eol_cells = column - i;
	}

	/* count the number of UTF-8 bytes for the given number of characters */
	off = 0;
	while (num_chars > 0 && off < buffer->len) {
		off++;
		if ((buffer->str[off] & 0xC0) != 0x80) num_chars--;
	}
	offset->text_offset = records[0].text_start_offset + off;
	return TRUE;
}


/* Given a row number and a VteCellTextOffset, compute the column within that row.
   It's the caller's responsibility to ensure that VteCellTextOffset really falls into that row.
   Requires the row to be frozen, or be outsize the range covered by the ring.
 */
static gboolean
_vte_frozen_row_text_offset_to_column (VteRing *ring,
				       gulong position,
				       const VteCellTextOffset *offset,
				       long *column)
{
	VteRowRecord records[2];
	VteCell *cell;
	GString *buffer = ring->utf8_buffer;
	const VteRowData *row;
	unsigned int i, off, num_chars, nc;

	if (position >= ring->end) {
		*column = offset->eol_cells;
		return TRUE;
	}

	if (G_UNLIKELY (position < ring->start)) {
		/* This happens when the marker (saved cursor position) is
		   scrolled off at the top of the scrollback buffer. */
		*column = 0;
		return TRUE;
	}

	g_assert(position < ring->writable);
	if (!_vte_ring_read_row_record (ring, &records[0], position))
		return FALSE;
	if ((position + 1) * sizeof (records[0]) < _vte_stream_head (ring->row_stream)) {
		if (!_vte_ring_read_row_record (ring, &records[1], position + 1))
			return FALSE;
	} else
		records[1].text_start_offset = _vte_stream_head (ring->text_stream);

	g_assert(offset->text_offset >= records[0].text_start_offset && offset->text_offset < records[1].text_start_offset);

	g_string_set_size (buffer, records[1].text_start_offset - records[0].text_start_offset);
	if (!_vte_stream_read (ring->text_stream, records[0].text_start_offset, buffer->str, buffer->len))
		return FALSE;

	if (G_LIKELY (buffer->len && buffer->str[buffer->len - 1] == '\n'))
		buffer->len--;

	row = _vte_ring_index(ring, position);

	/* row and buffer now contain the same text, in different representation */

	/* count the number of characters for the given UTF-8 text offset */
	off = offset->text_offset - records[0].text_start_offset;
	num_chars = 0;<commit_msg>ring: Make assertion more informative<commit_after>/*
 * Copyright (C) 2002,2009,2010 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.1 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Red Hat Author(s): <NAME>, <NAME>
 */

#include <config.h>

#include "debug.h"
#include "ring.h"

#include <string.h>

/*
 * VteRing: A buffer ring
 */

#ifdef VTE_DEBUG
static void
_vte_ring_validate (VteRing * ring)
{
	g_assert(ring != NULL);
	_vte_debug_print(VTE_DEBUG_RING,
			" Delta = %lu, Length = %lu, Next = %lu, Max = %lu, Writable = %lu.\n",
			ring->start, ring->end - ring->start, ring->end,
			ring->max, ring->end - ring->writable);

	g_assert (ring->start <= ring->writable);
	g_assert (ring->writable <= ring->end);

	g_assert (ring->end - ring->start <= ring->max);
	g_assert (ring->end - ring->writable <= ring->mask);
}
#else
#define _vte_ring_validate(ring) G_STMT_START {} G_STMT_END
#endif


void
_vte_ring_init (VteRing *ring, gulong max_rows, gboolean has_streams)
{
	_vte_debug_print(VTE_DEBUG_RING, "New ring %p.\n", ring);

	memset (ring, 0, sizeof (*ring));

	ring->max = MAX (max_rows, 3);

	ring->mask = 31;
	ring->array = (VteRowData *) g_malloc0 (sizeof (ring->array[0]) * (ring->mask + 1));

	ring->has_streams = has_streams;
	if (has_streams) {
		ring->attr_stream = _vte_file_stream_new ();
		ring->text_stream = _vte_file_stream_new ();
		ring->row_stream = _vte_file_stream_new ();
	} else {
		ring->attr_stream = ring->text_stream = ring->row_stream = NULL;
	}

	ring->last_attr_text_start_offset = 0;
	ring->last_attr.i = basic_cell.i.attr;
	ring->utf8_buffer = g_string_sized_new (128);

	_vte_row_data_init (&ring->cached_row);
	ring->cached_row_num = (gulong) -1;

        ring->visible_rows = 0;

	_vte_ring_validate(ring);
}

void
_vte_ring_fini (VteRing *ring)
{
	gulong i;

	for (i = 0; i <= ring->mask; i++)
		_vte_row_data_fini (&ring->array[i]);

	g_free (ring->array);

	if (ring->has_streams) {
		g_object_unref (ring->attr_stream);
		g_object_unref (ring->text_stream);
		g_object_unref (ring->row_stream);
	}

	g_string_free (ring->utf8_buffer, TRUE);

	_vte_row_data_fini (&ring->cached_row);
}

typedef struct _VteRowRecord {
	gsize text_start_offset;  /* offset where text of this row begins */
	gsize attr_start_offset;  /* offset of the first character's attributes */
	int soft_wrapped: 1;      /* end of line is not '\n' */
	int is_ascii: 1;          /* for rewrapping speedup: guarantees that line contains 32..126 bytes only. Can be 0 even when ascii only. */
} VteRowRecord;

/* Represents a cell position, see ../doc/rewrap.txt */
typedef struct _VteCellTextOffset {
	gsize text_offset;    /* byte offset in text_stream (or perhaps beyond) */
	gint fragment_cells;  /* extra number of cells to walk within a multicell character */
	gint eol_cells;       /* -1 if over a character, >=0 if at EOL or beyond */
} VteCellTextOffset;

static gboolean
_vte_ring_read_row_record (VteRing *ring, VteRowRecord *record, gulong position)
{
	return _vte_stream_read (ring->row_stream, position * sizeof (*record), (char *) record, sizeof (*record));
}

static void
_vte_ring_append_row_record (VteRing *ring, const VteRowRecord *record, gulong position)
{
	_vte_stream_append (ring->row_stream, (const char *) record, sizeof (*record));
}

static void
_vte_ring_freeze_row (VteRing *ring, gulong position, const VteRowData *row)
{
	VteRowRecord record;
	VteCell *cell;
	GString *buffer = ring->utf8_buffer;
	int i;

	_vte_debug_print (VTE_DEBUG_RING, "Freezing row %lu.\n", position);

        g_assert(ring->has_streams);

	memset(&record, 0, sizeof (record));
	record.text_start_offset = _vte_stream_head (ring->text_stream);
	record.attr_start_offset = _vte_stream_head (ring->attr_stream);
	record.is_ascii = 1;

	g_string_set_size (buffer, 0);
	for (i = 0, cell = row->cells; i < row->len; i++, cell++) {
		VteIntCellAttr attr;
		int num_chars;

		/* Attr storage:
		 *
		 * 1. We don't store attrs for fragments.  They can be
		 * reconstructed using the columns of their start cell.
		 *
		 * 2. We store one attr per vteunistr character starting
		 * from the second character, with columns=0.
		 *
		 * That's enough to reconstruct the attrs, and to store
		 * the text in real UTF-8.
		 */
		attr.s = cell->attr;
		if (G_LIKELY (!attr.s.fragment)) {
			VteCellAttrChange attr_change;

			if (ring->last_attr.i != attr.i) {
				ring->last_attr_text_start_offset = record.text_start_offset + buffer->len;
				memset(&attr_change, 0, sizeof (attr_change));
				attr_change.text_end_offset = ring->last_attr_text_start_offset;
				attr_change.attr = ring->last_attr;
				_vte_stream_append (ring->attr_stream, (const char *) &attr_change, sizeof (attr_change));
				if (!buffer->len)
					/* This row doesn't use last_attr, adjust */
					record.attr_start_offset += sizeof (attr_change);
				ring->last_attr = attr;
			}

			num_chars = _vte_unistr_strlen (cell->c);
			if (num_chars > 1) {
				attr.s.columns = 0;
				ring->last_attr_text_start_offset = record.text_start_offset + buffer->len
								  + g_unichar_to_utf8 (_vte_unistr_get_base (cell->c), NULL);
				memset(&attr_change, 0, sizeof (attr_change));
				attr_change.text_end_offset = ring->last_attr_text_start_offset;
				attr_change.attr = ring->last_attr;
				_vte_stream_append (ring->attr_stream, (const char *) &attr_change, sizeof (attr_change));
				ring->last_attr = attr;
			}

			if (cell->c < 32 || cell->c > 126) record.is_ascii = 0;
			_vte_unistr_append_to_string (cell->c, buffer);
		}
	}
	if (!row->attr.soft_wrapped)
		g_string_append_c (buffer, '\n');
	record.soft_wrapped = row->attr.soft_wrapped;

	_vte_stream_append (ring->text_stream, buffer->str, buffer->len);
	_vte_ring_append_row_record (ring, &record, position);
}

static void
_vte_ring_thaw_row (VteRing *ring, gulong position, VteRowData *row, gboolean do_truncate)
{
	VteRowRecord records[2], record;
	VteIntCellAttr attr;
	VteCellAttrChange attr_change;
	VteCell cell;
	const char *p, *q, *end;
	GString *buffer = ring->utf8_buffer;

	_vte_debug_print (VTE_DEBUG_RING, "Thawing row %lu.\n", position);

        g_assert(ring->has_streams);

	_vte_row_data_clear (row);

	attr_change.text_end_offset = 0;

	if (!_vte_ring_read_row_record (ring, &records[0], position))
		return;
	if ((position + 1) * sizeof (records[0]) < _vte_stream_head (ring->row_stream)) {
		if (!_vte_ring_read_row_record (ring, &records[1], position + 1))
			return;
	} else
		records[1].text_start_offset = _vte_stream_head (ring->text_stream);

	g_string_set_size (buffer, records[1].text_start_offset - records[0].text_start_offset);
	if (!_vte_stream_read (ring->text_stream, records[0].text_start_offset, buffer->str, buffer->len))
		return;

	record = records[0];

	if (G_LIKELY (buffer->len && buffer->str[buffer->len - 1] == '\n'))
		buffer->len--;
	else
		row->attr.soft_wrapped = TRUE;

	p = buffer->str;
	end = p + buffer->len;
	while (p < end) {

		if (record.text_start_offset >= ring->last_attr_text_start_offset) {
			attr = ring->last_attr;
		} else {
			if (record.text_start_offset >= attr_change.text_end_offset) {
				if (!_vte_stream_read (ring->attr_stream, record.attr_start_offset, (char *) &attr_change, sizeof (attr_change)))
					return;
				record.attr_start_offset += sizeof (attr_change);
			}
			attr = attr_change.attr;
		}

		cell.attr = attr.s;
		cell.c = g_utf8_get_char (p);

		q = g_utf8_next_char (p);
		record.text_start_offset += q - p;
		p = q;

		if (G_UNLIKELY (cell.attr.columns == 0)) {
			if (G_LIKELY (row->len)) {
				/* Combine it */
				row->cells[row->len - 1].c = _vte_unistr_append_unichar (row->cells[row->len - 1].c, cell.c);
			} else {
				cell.attr.columns = 1;
				_vte_row_data_append (row, &cell);
			}
		} else {
			_vte_row_data_append (row, &cell);
			if (cell.attr.columns > 1) {
				/* Add the fragments */
				int i, columns = cell.attr.columns;
				cell.attr.fragment = 1;
				cell.attr.columns = 1;
				for (i = 1; i < columns; i++)
					_vte_row_data_append (row, &cell);
			}
		}
	}

	if (do_truncate) {
		gsize attr_stream_truncate_at = records[0].attr_start_offset;
		_vte_debug_print (VTE_DEBUG_RING, "Truncating\n");
		if (records[0].text_start_offset <= ring->last_attr_text_start_offset) {
			/* Check the previous attr record. If its text ends where truncating, this attr record also needs to be removed. */
			if (_vte_stream_read (ring->attr_stream, attr_stream_truncate_at - sizeof (attr_change), (char *) &attr_change, sizeof (attr_change))) {
				if (records[0].text_start_offset == attr_change.text_end_offset) {
					_vte_debug_print (VTE_DEBUG_RING, "... at attribute change\n");
					attr_stream_truncate_at -= sizeof (attr_change);
				}
			}
			/* Reconstruct last_attr from the first record of attr_stream that we cut off,
			   last_attr_text_start_offset from the last record that we keep. */
			if (_vte_stream_read (ring->attr_stream, attr_stream_truncate_at, (char *) &attr_change, sizeof (attr_change))) {
				ring->last_attr = attr_change.attr;
				if (_vte_stream_read (ring->attr_stream, attr_stream_truncate_at - sizeof (attr_change), (char *) &attr_change, sizeof (attr_change))) {
					ring->last_attr_text_start_offset = attr_change.text_end_offset;
				} else {
					ring->last_attr_text_start_offset = 0;
				}
			} else {
				ring->last_attr_text_start_offset = 0;
				ring->last_attr.i = basic_cell.i.attr;
			}
		}
		_vte_stream_truncate (ring->row_stream, position * sizeof (record));
		_vte_stream_truncate (ring->attr_stream, attr_stream_truncate_at);
		_vte_stream_truncate (ring->text_stream, records[0].text_start_offset);
	}
}

static void
_vte_ring_reset_streams (VteRing *ring, gulong position)
{
	_vte_debug_print (VTE_DEBUG_RING, "Reseting streams to %lu.\n", position);

	if (ring->has_streams) {
		_vte_stream_reset (ring->row_stream, position * sizeof (VteRowRecord));
                _vte_stream_reset (ring->text_stream, _vte_stream_head (ring->text_stream));
                _vte_stream_reset (ring->attr_stream, _vte_stream_head (ring->attr_stream));
	}

	ring->last_attr_text_start_offset = 0;
	ring->last_attr.i = basic_cell.i.attr;
}

long
_vte_ring_reset (VteRing *ring)
{
        _vte_debug_print (VTE_DEBUG_RING, "Reseting the ring at %lu.\n", ring->end);

        _vte_ring_reset_streams (ring, ring->end);
        ring->start = ring->writable = ring->end;
        ring->cached_row_num = (gulong) -1;

        return ring->end;
}

static inline VteRowData *
_vte_ring_writable_index (VteRing *ring, gulong position)
{
	return &ring->array[position & ring->mask];
}

const VteRowData *
_vte_ring_index (VteRing *ring, gulong position)
{
	if (G_LIKELY (position >= ring->writable))
		return _vte_ring_writable_index (ring, position);

	if (ring->cached_row_num != position) {
		_vte_debug_print(VTE_DEBUG_RING, "Caching row %lu.\n", position);
		_vte_ring_thaw_row (ring, position, &ring->cached_row, FALSE);
		ring->cached_row_num = position;
	}

	return &ring->cached_row;
}

static void _vte_ring_ensure_writable (VteRing *ring, gulong position);
static void _vte_ring_ensure_writable_room (VteRing *ring);

VteRowData *
_vte_ring_index_writable (VteRing *ring, gulong position)
{
	_vte_ring_ensure_writable (ring, position);
	return _vte_ring_writable_index (ring, position);
}

static void
_vte_ring_freeze_one_row (VteRing *ring)
{
	VteRowData *row;

	if (G_UNLIKELY (ring->writable == ring->start))
		_vte_ring_reset_streams (ring, ring->writable);

	row = _vte_ring_writable_index (ring, ring->writable);
	_vte_ring_freeze_row (ring, ring->writable, row);

	ring->writable++;
}

static void
_vte_ring_thaw_one_row (VteRing *ring)
{
	VteRowData *row;

	g_assert (ring->start < ring->writable);

	_vte_ring_ensure_writable_room (ring);

	ring->writable--;

	if (ring->writable == ring->cached_row_num)
		ring->cached_row_num = (gulong) -1; /* Invalidate cached row */

	row = _vte_ring_writable_index (ring, ring->writable);

	_vte_ring_thaw_row (ring, ring->writable, row, TRUE);
}

static void
_vte_ring_discard_one_row (VteRing *ring)
{
	ring->start++;
	if (G_UNLIKELY (ring->start == ring->writable)) {
		_vte_ring_reset_streams (ring, ring->writable);
	} else if (ring->start < ring->writable) {
		VteRowRecord record;
		_vte_stream_advance_tail (ring->row_stream, ring->start * sizeof (record));
		if (G_LIKELY (_vte_ring_read_row_record (ring, &record, ring->start))) {
			_vte_stream_advance_tail (ring->text_stream, record.text_start_offset);
			_vte_stream_advance_tail (ring->attr_stream, record.attr_start_offset);
		}
	} else {
		ring->writable = ring->start;
	}
}

static void
_vte_ring_maybe_freeze_one_row (VteRing *ring)
{
        if (G_LIKELY (ring->mask >= ring->visible_rows && ring->writable + ring->mask + 1 == ring->end))
		_vte_ring_freeze_one_row (ring);
	else
		_vte_ring_ensure_writable_room (ring);
}

static void
_vte_ring_maybe_discard_one_row (VteRing *ring)
{
	if ((gulong) _vte_ring_length (ring) == ring->max)
		_vte_ring_discard_one_row (ring);
}

static void
_vte_ring_ensure_writable_room (VteRing *ring)
{
	gulong new_mask, old_mask, i, end;
	VteRowData *old_array, *new_array;;

        if (G_LIKELY (ring->mask >= ring->visible_rows && ring->writable + ring->mask + 1 > ring->end))
		return;

	old_mask = ring->mask;
	old_array = ring->array;

	do {
		ring->mask = (ring->mask << 1) + 1;
        } while (ring->mask < ring->visible_rows || ring->writable + ring->mask + 1 <= ring->end);

	_vte_debug_print(VTE_DEBUG_RING, "Enlarging writable array from %lu to %lu\n", old_mask, ring->mask);

	ring->array = (VteRowData *) g_malloc0 (sizeof (ring->array[0]) * (ring->mask + 1));

	new_mask = ring->mask;
	new_array = ring->array;

	end = ring->writable + old_mask + 1;
	for (i = ring->writable; i < end; i++)
		new_array[i & new_mask] = old_array[i & old_mask];

	g_free (old_array);
}

static void
_vte_ring_ensure_writable (VteRing *ring, gulong position)
{
	if (G_LIKELY (position >= ring->writable))
		return;

	_vte_debug_print(VTE_DEBUG_RING, "Ensure writable %lu.\n", position);

	while (position < ring->writable)
		_vte_ring_thaw_one_row (ring);
}

/**
 * _vte_ring_resize:
 * @ring: a #VteRing
 * @max_rows: new maximum numbers of rows in the ring
 *
 * Changes the number of lines the ring can contain.
 */
void
_vte_ring_resize (VteRing *ring, gulong max_rows)
{
	_vte_debug_print(VTE_DEBUG_RING, "Resizing to %lu.\n", max_rows);
	_vte_ring_validate(ring);

	/* Adjust the start of tail chunk now */
	if ((gulong) _vte_ring_length (ring) > max_rows) {
		ring->start = ring->end - max_rows;
		if (ring->start >= ring->writable) {
			_vte_ring_reset_streams (ring, ring->writable);
			ring->writable = ring->start;
		}
	}

	ring->max = max_rows;
}

void
_vte_ring_shrink (VteRing *ring, gulong max_len)
{
	if ((gulong) _vte_ring_length (ring) <= max_len)
		return;

	_vte_debug_print(VTE_DEBUG_RING, "Shrinking to %lu.\n", max_len);
	_vte_ring_validate(ring);

	if (ring->writable - ring->start <= max_len)
		ring->end = ring->start + max_len;
	else {
		while (ring->writable - ring->start > max_len) {
			_vte_ring_ensure_writable (ring, ring->writable - 1);
			ring->end = ring->writable;
		}
	}

	/* TODO May want to shrink down ring->array */

	_vte_ring_validate(ring);
}

/**
 * _vte_ring_insert_internal:
 * @ring: a #VteRing
 * @position: an index
 *
 * Inserts a new, empty, row into @ring at the @position'th offset.
 * The item at that position and any items after that are shifted down.
 *
 * Return: the newly added row.
 */
VteRowData *
_vte_ring_insert (VteRing *ring, gulong position)
{
	gulong i;
	VteRowData *row, tmp;

	_vte_debug_print(VTE_DEBUG_RING, "Inserting at position %lu.\n", position);
	_vte_ring_validate(ring);

	_vte_ring_maybe_discard_one_row (ring);

	_vte_ring_ensure_writable (ring, position);
	_vte_ring_ensure_writable_room (ring);

	g_assert (position >= ring->writable && position <= ring->end);

	tmp = *_vte_ring_writable_index (ring, ring->end);
	for (i = ring->end; i > position; i--)
		*_vte_ring_writable_index (ring, i) = *_vte_ring_writable_index (ring, i - 1);
	*_vte_ring_writable_index (ring, position) = tmp;

	row = _vte_ring_writable_index (ring, position);
	_vte_row_data_clear (row);
	ring->end++;

	_vte_ring_maybe_freeze_one_row (ring);

	_vte_ring_validate(ring);
	return row;
}

/**
 * _vte_ring_remove:
 * @ring: a #VteRing
 * @position: an index
 *
 * Removes the @position'th item from @ring.
 */
void
_vte_ring_remove (VteRing * ring, gulong position)
{
	gulong i;
	VteRowData tmp;

	_vte_debug_print(VTE_DEBUG_RING, "Removing item at position %lu.\n", position);
	_vte_ring_validate(ring);

	if (G_UNLIKELY (!_vte_ring_contains (ring, position)))
		return;

	_vte_ring_ensure_writable (ring, position);

	tmp = *_vte_ring_writable_index (ring, position);
	for (i = position; i < ring->end - 1; i++)
		*_vte_ring_writable_index (ring, i) = *_vte_ring_writable_index (ring, i + 1);
	*_vte_ring_writable_index (ring, ring->end - 1) = tmp;

	if (ring->end > ring->writable)
		ring->end--;

	_vte_ring_validate(ring);
}


/**
 * _vte_ring_append:
 * @ring: a #VteRing
 * @data: the new item
 *
 * Appends a new item to the ring.
 *
 * Return: the newly added row.
 */
VteRowData *
_vte_ring_append (VteRing * ring)
{
	return _vte_ring_insert (ring, _vte_ring_next (ring));
}


/**
 * _vte_ring_drop_scrollback:
 * @ring: a #VteRing
 * @position: drop contents up to this point, which must be in the writable region.
 *
 * Drop the scrollback (offscreen contents).
 *
 * TODOegmont: We wouldn't need the position argument after addressing 708213#c29.
 */
void
_vte_ring_drop_scrollback (VteRing * ring, gulong position)
{
        _vte_ring_ensure_writable (ring, position);

        ring->start = ring->writable = position;
        _vte_ring_reset_streams (ring, position);
}


/**
 * _vte_ring_set_visible_rows:
 * @ring: a #VteRing
 *
 * Set the number of visible rows.
 * It's required to be set correctly for the alternate screen so that it
 * never hits the streams. It's also required for clearing the scrollback.
 */
void
_vte_ring_set_visible_rows (VteRing *ring, gulong rows)
{
        ring->visible_rows = rows;
}


/* Convert a (row,col) into a VteCellTextOffset.
 * Requires the row to be frozen, or be outsize the range covered by the ring.
 */
static gboolean
_vte_frozen_row_column_to_text_offset (VteRing *ring,
				       gulong position,
				       gulong column,
				       VteCellTextOffset *offset)
{
	VteRowRecord records[2];
	VteCell *cell;
	GString *buffer = ring->utf8_buffer;
	const VteRowData *row;
	unsigned int i, num_chars, off;

	if (position >= ring->end) {
		offset->text_offset = _vte_stream_head (ring->text_stream) + position - ring->end;
		offset->fragment_cells = 0;
		offset->eol_cells = column;
		return TRUE;
	}

	if (G_UNLIKELY (position < ring->start)) {
		/* This happens when the marker (saved cursor position) is
		   scrolled off at the top of the scrollback buffer. */
		position = ring->start;
		column = 0;
		/* go on */
	}

	g_assert(position < ring->writable);
	if (!_vte_ring_read_row_record (ring, &records[0], position))
		return FALSE;
	if ((position + 1) * sizeof (records[0]) < _vte_stream_head (ring->row_stream)) {
		if (!_vte_ring_read_row_record (ring, &records[1], position + 1))
			return FALSE;
	} else
		records[1].text_start_offset = _vte_stream_head (ring->text_stream);

	g_string_set_size (buffer, records[1].text_start_offset - records[0].text_start_offset);
	if (!_vte_stream_read (ring->text_stream, records[0].text_start_offset, buffer->str, buffer->len))
		return FALSE;

	if (G_LIKELY (buffer->len && buffer->str[buffer->len - 1] == '\n'))
		buffer->len--;

	row = _vte_ring_index(ring, position);

	/* row and buffer now contain the same text, in different representation */

	/* count the number of characters up to the given column */
	offset->fragment_cells = 0;
	offset->eol_cells = -1;
	num_chars = 0;
	for (i = 0, cell = row->cells; i < row->len && i < column; i++, cell++) {
		if (G_LIKELY (!cell->attr.fragment)) {
			if (G_UNLIKELY (i + cell->attr.columns > column)) {
				offset->fragment_cells = column - i;
				break;
			}
			num_chars += _vte_unistr_strlen(cell->c);
		}
	}
	if (i >= row->len) {
		offset->eol_cells = column - i;
	}

	/* count the number of UTF-8 bytes for the given number of characters */
	off = 0;
	while (num_chars > 0 && off < buffer->len) {
		off++;
		if ((buffer->str[off] & 0xC0) != 0x80) num_chars--;
	}
	offset->text_offset = records[0].text_start_offset + off;
	return TRUE;
}


/* Given a row number and a VteCellTextOffset, compute the column within that row.
   It's the caller's responsibility to ensure that VteCellTextOffset really falls into that row.
   Requires the row to be frozen, or be outsize the range covered by the ring.
 */
static gboolean
_vte_frozen_row_text_offset_to_column (VteRing *ring,
				       gulong position,
				       const VteCellTextOffset *offset,
				       long *column)
{
	VteRowRecord records[2];
	VteCell *cell;
	GString *buffer = ring->utf8_buffer;
	const VteRowData *row;
	unsigned int i, off, num_chars, nc;

	if (position >= ring->end) {
		*column = offset->eol_cells;
		return TRUE;
	}

	if (G_UNLIKELY (position < ring->start)) {
		/* This happens when the marker (saved cursor position) is
		   scrolled off at the top of the scrollback buffer. */
		*column = 0;
		return TRUE;
	}

	g_assert_cmpuint(position, <, ring->writable);
	if (!_vte_ring_read_row_record (ring, &records[0], position))
		return FALSE;
	if ((position + 1) * sizeof (records[0]) < _vte_stream_head (ring->row_stream)) {
		if (!_vte_ring_read_row_record (ring, &records[1], position + 1))
			return FALSE;
	} else
		records[1].text_start_offset = _vte_stream_head (ring->text_stream);

	g_assert(offset->text_offset >= records[0].text_start_offset && offset->text_offset < records[1].text_start_offset);

	g_string_set_size (buffer, records[1].text_start_offset - records[0].text_start_offset);
	if (!_vte_stream_read (ring->text_stream, records[0].text_start_offset, buffer->str, buffer->len))
		return FALSE;

	if (G_LIKELY (buffer->len && buffer->str[buffer->len - 1] == '\n'))
		buffer->len--;

	row = _vte_ring_index(ring, position);

	/* row and buffer now contain the same text, in different representation */

	/* count the number of characters for the given UTF-8 text offset */
	off = offset->text_offset - records[0].text_start_offset;
	num_chars = 0;
{'fraction_non_alphanumeric': 0.13024718126626192, 'fraction_numerical': 0.007415437987857762, 'mean_word_length': 4.212001356085433, 'pattern_counts': {'":': 0, '<': 77, '<?xml version=': 0, '>': 597, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3040033', 'n_tokens_mistral': 18411, 'n_tokens_neox': 16997, 'n_words': 4085}