xed/xedit/xedit-document-input-stream.c

480 lines
12 KiB
C
Raw Permalink Normal View History

2011-11-07 13:46:58 -06:00
/*
2016-01-25 08:13:49 -06:00
* xedit-document-input-stream.c
* This file is part of xedit
2011-11-07 13:46:58 -06:00
*
* Copyright (C) 2010 - Ignacio Casal Quinteiro
*
2016-01-25 08:13:49 -06:00
* xedit is free software; you can redistribute it and/or modify
2011-11-07 13:46:58 -06:00
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
2016-01-25 08:13:49 -06:00
* xedit is distributed in the hope that it will be useful,
2011-11-07 13:46:58 -06:00
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
2016-01-25 08:13:49 -06:00
* along with xedit; if not, write to the Free Software
2011-11-07 13:46:58 -06:00
* Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
#include "config.h"
#include <glib.h>
#include <gio/gio.h>
#include <string.h>
2016-01-25 08:13:49 -06:00
#include "xedit-document-input-stream.h"
#include "xedit-enum-types.h"
2011-11-07 13:46:58 -06:00
/* NOTE: never use async methods on this stream, the stream is just
* a wrapper around GtkTextBuffer api so that we can use GIO Stream
* methods, but the undelying code operates on a GtkTextBuffer, so
* there is no I/O involved and should be accessed only by the main
* thread */
2016-01-25 08:13:49 -06:00
G_DEFINE_TYPE (XeditDocumentInputStream, xedit_document_input_stream, G_TYPE_INPUT_STREAM);
2011-11-07 13:46:58 -06:00
2016-01-25 08:13:49 -06:00
struct _XeditDocumentInputStreamPrivate
2011-11-07 13:46:58 -06:00
{
GtkTextBuffer *buffer;
GtkTextMark *pos;
gint bytes_partial;
2016-01-25 08:13:49 -06:00
XeditDocumentNewlineType newline_type;
2011-11-07 13:46:58 -06:00
guint newline_added : 1;
guint is_initialized : 1;
};
enum
{
PROP_0,
PROP_BUFFER,
PROP_NEWLINE_TYPE
};
2016-01-25 08:13:49 -06:00
static gssize xedit_document_input_stream_read (GInputStream *stream,
2011-11-07 13:46:58 -06:00
void *buffer,
gsize count,
GCancellable *cancellable,
GError **error);
2016-01-25 08:13:49 -06:00
static gboolean xedit_document_input_stream_close (GInputStream *stream,
2011-11-07 13:46:58 -06:00
GCancellable *cancellable,
GError **error);
static void
2016-01-25 08:13:49 -06:00
xedit_document_input_stream_set_property (GObject *object,
2011-11-07 13:46:58 -06:00
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
2016-01-25 08:13:49 -06:00
XeditDocumentInputStream *stream = XEDIT_DOCUMENT_INPUT_STREAM (object);
2011-11-07 13:46:58 -06:00
switch (prop_id)
{
case PROP_BUFFER:
stream->priv->buffer = GTK_TEXT_BUFFER (g_value_get_object (value));
break;
case PROP_NEWLINE_TYPE:
stream->priv->newline_type = g_value_get_enum (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
2016-01-25 08:13:49 -06:00
xedit_document_input_stream_get_property (GObject *object,
2011-11-07 13:46:58 -06:00
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
2016-01-25 08:13:49 -06:00
XeditDocumentInputStream *stream = XEDIT_DOCUMENT_INPUT_STREAM (object);
2011-11-07 13:46:58 -06:00
switch (prop_id)
{
case PROP_BUFFER:
g_value_set_object (value, stream->priv->buffer);
break;
case PROP_NEWLINE_TYPE:
g_value_set_enum (value, stream->priv->newline_type);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
2016-01-25 08:13:49 -06:00
xedit_document_input_stream_class_init (XeditDocumentInputStreamClass *klass)
2011-11-07 13:46:58 -06:00
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GInputStreamClass *stream_class = G_INPUT_STREAM_CLASS (klass);
2016-01-25 08:13:49 -06:00
g_type_class_add_private (klass, sizeof (XeditDocumentInputStreamPrivate));
2011-11-07 13:46:58 -06:00
2016-01-25 08:13:49 -06:00
gobject_class->get_property = xedit_document_input_stream_get_property;
gobject_class->set_property = xedit_document_input_stream_set_property;
2011-11-07 13:46:58 -06:00
2016-01-25 08:13:49 -06:00
stream_class->read_fn = xedit_document_input_stream_read;
stream_class->close_fn = xedit_document_input_stream_close;
2011-11-07 13:46:58 -06:00
g_object_class_install_property (gobject_class,
PROP_BUFFER,
g_param_spec_object ("buffer",
"Buffer",
"The buffer which is read",
GTK_TYPE_TEXT_BUFFER,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
/**
2016-01-25 08:13:49 -06:00
* XeditDocumentInputStream:newline-type:
2011-11-07 13:46:58 -06:00
*
* The :newline-type property determines what is considered
* as a line ending when reading complete lines from the stream.
*/
g_object_class_install_property (gobject_class,
PROP_NEWLINE_TYPE,
g_param_spec_enum ("newline-type",
"Newline type",
"The accepted types of line ending",
2016-01-25 08:13:49 -06:00
XEDIT_TYPE_DOCUMENT_NEWLINE_TYPE,
XEDIT_DOCUMENT_NEWLINE_TYPE_LF,
2011-11-07 13:46:58 -06:00
G_PARAM_READWRITE |
G_PARAM_STATIC_NAME |
G_PARAM_STATIC_BLURB |
G_PARAM_CONSTRUCT_ONLY));
}
static void
2016-01-25 08:13:49 -06:00
xedit_document_input_stream_init (XeditDocumentInputStream *stream)
2011-11-07 13:46:58 -06:00
{
stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream,
2016-01-25 08:13:49 -06:00
XEDIT_TYPE_DOCUMENT_INPUT_STREAM,
XeditDocumentInputStreamPrivate);
2011-11-07 13:46:58 -06:00
}
static gsize
2016-01-25 08:13:49 -06:00
get_new_line_size (XeditDocumentInputStream *stream)
2011-11-07 13:46:58 -06:00
{
gsize ret;
switch (stream->priv->newline_type)
{
2016-01-25 08:13:49 -06:00
case XEDIT_DOCUMENT_NEWLINE_TYPE_CR:
case XEDIT_DOCUMENT_NEWLINE_TYPE_LF:
2011-11-07 13:46:58 -06:00
ret = 1;
break;
2016-01-25 08:13:49 -06:00
case XEDIT_DOCUMENT_NEWLINE_TYPE_CR_LF:
2011-11-07 13:46:58 -06:00
ret = 2;
break;
default:
g_warn_if_reached ();
ret = 1;
break;
}
return ret;
}
/**
2016-01-25 08:13:49 -06:00
* xedit_document_input_stream_new:
2011-11-07 13:46:58 -06:00
* @buffer: a #GtkTextBuffer
*
* Reads the data from @buffer.
*
* Returns: a new #GInputStream to read @buffer
*/
GInputStream *
2016-01-25 08:13:49 -06:00
xedit_document_input_stream_new (GtkTextBuffer *buffer,
XeditDocumentNewlineType type)
2011-11-07 13:46:58 -06:00
{
2016-01-25 08:13:49 -06:00
XeditDocumentInputStream *stream;
2011-11-07 13:46:58 -06:00
g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
2016-01-25 08:13:49 -06:00
stream = g_object_new (XEDIT_TYPE_DOCUMENT_INPUT_STREAM,
2011-11-07 13:46:58 -06:00
"buffer", buffer,
"newline-type", type,
NULL);
return G_INPUT_STREAM (stream);
}
gsize
2016-01-25 08:13:49 -06:00
xedit_document_input_stream_get_total_size (XeditDocumentInputStream *stream)
2011-11-07 13:46:58 -06:00
{
2016-01-25 08:13:49 -06:00
g_return_val_if_fail (XEDIT_IS_DOCUMENT_INPUT_STREAM (stream), 0);
2011-11-07 13:46:58 -06:00
return gtk_text_buffer_get_char_count (stream->priv->buffer);
}
gsize
2016-01-25 08:13:49 -06:00
xedit_document_input_stream_tell (XeditDocumentInputStream *stream)
2011-11-07 13:46:58 -06:00
{
2016-01-25 08:13:49 -06:00
g_return_val_if_fail (XEDIT_IS_DOCUMENT_INPUT_STREAM (stream), 0);
2011-11-07 13:46:58 -06:00
/* FIXME: is this potentially inefficient? If yes, we could keep
track of the offset internally, assuming the mark doesn't move
during the operation */
if (!stream->priv->is_initialized)
{
return 0;
}
else
{
GtkTextIter iter;
gtk_text_buffer_get_iter_at_mark (stream->priv->buffer,
&iter,
stream->priv->pos);
return gtk_text_iter_get_offset (&iter);
}
}
static const gchar *
2016-01-25 08:13:49 -06:00
get_new_line (XeditDocumentInputStream *stream)
2011-11-07 13:46:58 -06:00
{
const gchar *ret;
switch (stream->priv->newline_type)
{
2016-01-25 08:13:49 -06:00
case XEDIT_DOCUMENT_NEWLINE_TYPE_CR:
2011-11-07 13:46:58 -06:00
ret = "\r";
break;
2016-01-25 08:13:49 -06:00
case XEDIT_DOCUMENT_NEWLINE_TYPE_LF:
2011-11-07 13:46:58 -06:00
ret = "\n";
break;
2016-01-25 08:13:49 -06:00
case XEDIT_DOCUMENT_NEWLINE_TYPE_CR_LF:
2011-11-07 13:46:58 -06:00
ret = "\r\n";
break;
default:
g_warn_if_reached ();
ret = "\n";
break;
}
return ret;
}
static gsize
2016-01-25 08:13:49 -06:00
read_line (XeditDocumentInputStream *stream,
2011-11-07 13:46:58 -06:00
gchar *outbuf,
gsize space_left)
{
GtkTextIter start, next, end;
gchar *buf;
gint bytes; /* int since it's what iter_get_offset returns */
gsize bytes_to_write, newline_size, read;
const gchar *newline;
gboolean is_last;
gtk_text_buffer_get_iter_at_mark (stream->priv->buffer,
&start,
stream->priv->pos);
if (gtk_text_iter_is_end (&start))
return 0;
end = next = start;
newline = get_new_line (stream);
/* Check needed for empty lines */
if (!gtk_text_iter_ends_line (&end))
gtk_text_iter_forward_to_line_end (&end);
gtk_text_iter_forward_line (&next);
buf = gtk_text_iter_get_slice (&start, &end);
/* the bytes of a line includes also the newline, so with the
offsets we remove the newline and we add the new newline size */
bytes = gtk_text_iter_get_bytes_in_line (&start) - stream->priv->bytes_partial;
/* bytes_in_line includes the newlines, so we remove that assuming that
they are single byte characters */
bytes = bytes - (gtk_text_iter_get_offset (&next) - gtk_text_iter_get_offset (&end));
is_last = gtk_text_iter_is_end (&end);
/* bytes_to_write contains the amount of bytes we would like to write.
This means its the amount of bytes in the line (without the newline
in the buffer) + the amount of bytes for the newline we want to
write (newline_size) */
bytes_to_write = bytes;
/* do not add the new newline_size for the last line */
newline_size = get_new_line_size (stream);
if (!is_last)
bytes_to_write += newline_size;
if (bytes_to_write > space_left)
{
gchar *ptr;
gint char_offset;
gint written;
gsize to_write;
/* Here the line does not fit in the buffer, we thus write
the amount of bytes we can still fit, storing the position
for the next read with the mark. Do not try to write the
new newline in this case, it will be handled in the next
iteration */
to_write = MIN (space_left, bytes);
ptr = buf;
written = 0;
char_offset = 0;
while (written < to_write)
{
gint w;
ptr = g_utf8_next_char (ptr);
w = (ptr - buf);
if (w > to_write)
{
break;
}
else
{
written = w;
++char_offset;
}
}
memcpy (outbuf, buf, written);
/* Note: offset is one past what we wrote */
gtk_text_iter_forward_chars (&start, char_offset);
stream->priv->bytes_partial += written;
read = written;
}
else
{
/* First just copy the bytes without the newline */
memcpy (outbuf, buf, bytes);
/* Then add the newline, but not for the last line */
if (!is_last)
{
memcpy (outbuf + bytes, newline, newline_size);
}
start = next;
stream->priv->bytes_partial = 0;
read = bytes_to_write;
}
gtk_text_buffer_move_mark (stream->priv->buffer,
stream->priv->pos,
&start);
g_free (buf);
return read;
}
static gssize
2016-01-25 08:13:49 -06:00
xedit_document_input_stream_read (GInputStream *stream,
2011-11-07 13:46:58 -06:00
void *buffer,
gsize count,
GCancellable *cancellable,
GError **error)
{
2016-01-25 08:13:49 -06:00
XeditDocumentInputStream *dstream;
2011-11-07 13:46:58 -06:00
GtkTextIter iter;
gssize space_left, read, n;
2016-01-25 08:13:49 -06:00
dstream = XEDIT_DOCUMENT_INPUT_STREAM (stream);
2011-11-07 13:46:58 -06:00
if (count < 6)
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE,
"Not enougth space in destination");
return -1;
}
if (g_cancellable_set_error_if_cancelled (cancellable, error))
return -1;
/* Initialize the mark to the first char in the text buffer */
if (!dstream->priv->is_initialized)
{
gtk_text_buffer_get_start_iter (dstream->priv->buffer, &iter);
dstream->priv->pos = gtk_text_buffer_create_mark (dstream->priv->buffer,
NULL,
&iter,
FALSE);
dstream->priv->is_initialized = TRUE;
}
space_left = count;
read = 0;
do
{
n = read_line (dstream, buffer + read, space_left);
read += n;
space_left -= n;
} while (space_left > 0 && n != 0 && dstream->priv->bytes_partial == 0);
/* Make sure that non-empty files are always terminated with \n (see bug #95676).
* Note that we strip the trailing \n when loading the file */
gtk_text_buffer_get_iter_at_mark (dstream->priv->buffer,
&iter,
dstream->priv->pos);
if (gtk_text_iter_is_end (&iter) &&
!gtk_text_iter_is_start (&iter))
{
gssize newline_size;
newline_size = get_new_line_size (dstream);
if (space_left >= newline_size &&
!dstream->priv->newline_added)
{
const gchar *newline;
newline = get_new_line (dstream);
memcpy (buffer + read, newline, newline_size);
read += newline_size;
dstream->priv->newline_added = TRUE;
}
}
return read;
}
static gboolean
2016-01-25 08:13:49 -06:00
xedit_document_input_stream_close (GInputStream *stream,
2011-11-07 13:46:58 -06:00
GCancellable *cancellable,
GError **error)
{
2016-01-25 08:13:49 -06:00
XeditDocumentInputStream *dstream = XEDIT_DOCUMENT_INPUT_STREAM (stream);
2011-11-07 13:46:58 -06:00
dstream->priv->newline_added = FALSE;
if (dstream->priv->is_initialized)
{
gtk_text_buffer_delete_mark (dstream->priv->buffer, dstream->priv->pos);
}
return TRUE;
}