summaryrefslogtreecommitdiff
path: root/gedit/gedit-document-input-stream.c
diff options
context:
space:
mode:
authorPerberos <[email protected]>2011-11-07 16:46:58 -0300
committerPerberos <[email protected]>2011-11-07 16:46:58 -0300
commit528c1e5ff51e213936e800fc5a9a25da99c0bdf2 (patch)
tree77f8aa456b09367ba81f04d4562fc935f898a951 /gedit/gedit-document-input-stream.c
downloadpluma-528c1e5ff51e213936e800fc5a9a25da99c0bdf2.tar.bz2
pluma-528c1e5ff51e213936e800fc5a9a25da99c0bdf2.tar.xz
initial
Diffstat (limited to 'gedit/gedit-document-input-stream.c')
-rwxr-xr-xgedit/gedit-document-input-stream.c479
1 files changed, 479 insertions, 0 deletions
diff --git a/gedit/gedit-document-input-stream.c b/gedit/gedit-document-input-stream.c
new file mode 100755
index 00000000..5a1f9614
--- /dev/null
+++ b/gedit/gedit-document-input-stream.c
@@ -0,0 +1,479 @@
+/*
+ * gedit-document-input-stream.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2010 - Ignacio Casal Quinteiro
+ *
+ * gedit is free software; you can redistribute it and/or modify
+ * 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.
+ *
+ * gedit 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gedit; if not, write to the Free Software
+ * 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>
+#include "gedit-document-input-stream.h"
+#include "gedit-enum-types.h"
+
+/* 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 */
+
+
+G_DEFINE_TYPE (GeditDocumentInputStream, gedit_document_input_stream, G_TYPE_INPUT_STREAM);
+
+struct _GeditDocumentInputStreamPrivate
+{
+ GtkTextBuffer *buffer;
+ GtkTextMark *pos;
+ gint bytes_partial;
+
+ GeditDocumentNewlineType newline_type;
+
+ guint newline_added : 1;
+ guint is_initialized : 1;
+};
+
+enum
+{
+ PROP_0,
+ PROP_BUFFER,
+ PROP_NEWLINE_TYPE
+};
+
+static gssize gedit_document_input_stream_read (GInputStream *stream,
+ void *buffer,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean gedit_document_input_stream_close (GInputStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+
+static void
+gedit_document_input_stream_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditDocumentInputStream *stream = GEDIT_DOCUMENT_INPUT_STREAM (object);
+
+ 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
+gedit_document_input_stream_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditDocumentInputStream *stream = GEDIT_DOCUMENT_INPUT_STREAM (object);
+
+ 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
+gedit_document_input_stream_class_init (GeditDocumentInputStreamClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GInputStreamClass *stream_class = G_INPUT_STREAM_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (GeditDocumentInputStreamPrivate));
+
+ gobject_class->get_property = gedit_document_input_stream_get_property;
+ gobject_class->set_property = gedit_document_input_stream_set_property;
+
+ stream_class->read_fn = gedit_document_input_stream_read;
+ stream_class->close_fn = gedit_document_input_stream_close;
+
+ 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));
+
+ /**
+ * GeditDocumentInputStream:newline-type:
+ *
+ * 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",
+ GEDIT_TYPE_DOCUMENT_NEWLINE_TYPE,
+ GEDIT_DOCUMENT_NEWLINE_TYPE_LF,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gedit_document_input_stream_init (GeditDocumentInputStream *stream)
+{
+ stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream,
+ GEDIT_TYPE_DOCUMENT_INPUT_STREAM,
+ GeditDocumentInputStreamPrivate);
+}
+
+static gsize
+get_new_line_size (GeditDocumentInputStream *stream)
+{
+ gsize ret;
+
+ switch (stream->priv->newline_type)
+ {
+ case GEDIT_DOCUMENT_NEWLINE_TYPE_CR:
+ case GEDIT_DOCUMENT_NEWLINE_TYPE_LF:
+ ret = 1;
+ break;
+
+ case GEDIT_DOCUMENT_NEWLINE_TYPE_CR_LF:
+ ret = 2;
+ break;
+
+ default:
+ g_warn_if_reached ();
+ ret = 1;
+ break;
+ }
+
+ return ret;
+}
+
+/**
+ * gedit_document_input_stream_new:
+ * @buffer: a #GtkTextBuffer
+ *
+ * Reads the data from @buffer.
+ *
+ * Returns: a new #GInputStream to read @buffer
+ */
+GInputStream *
+gedit_document_input_stream_new (GtkTextBuffer *buffer,
+ GeditDocumentNewlineType type)
+{
+ GeditDocumentInputStream *stream;
+
+ g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
+
+ stream = g_object_new (GEDIT_TYPE_DOCUMENT_INPUT_STREAM,
+ "buffer", buffer,
+ "newline-type", type,
+ NULL);
+
+ return G_INPUT_STREAM (stream);
+}
+
+gsize
+gedit_document_input_stream_get_total_size (GeditDocumentInputStream *stream)
+{
+ g_return_val_if_fail (GEDIT_IS_DOCUMENT_INPUT_STREAM (stream), 0);
+
+ return gtk_text_buffer_get_char_count (stream->priv->buffer);
+}
+
+gsize
+gedit_document_input_stream_tell (GeditDocumentInputStream *stream)
+{
+ g_return_val_if_fail (GEDIT_IS_DOCUMENT_INPUT_STREAM (stream), 0);
+
+ /* 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 *
+get_new_line (GeditDocumentInputStream *stream)
+{
+ const gchar *ret;
+
+ switch (stream->priv->newline_type)
+ {
+ case GEDIT_DOCUMENT_NEWLINE_TYPE_CR:
+ ret = "\r";
+ break;
+
+ case GEDIT_DOCUMENT_NEWLINE_TYPE_LF:
+ ret = "\n";
+ break;
+
+ case GEDIT_DOCUMENT_NEWLINE_TYPE_CR_LF:
+ ret = "\r\n";
+ break;
+
+ default:
+ g_warn_if_reached ();
+ ret = "\n";
+ break;
+ }
+
+ return ret;
+}
+
+static gsize
+read_line (GeditDocumentInputStream *stream,
+ 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
+gedit_document_input_stream_read (GInputStream *stream,
+ void *buffer,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GeditDocumentInputStream *dstream;
+ GtkTextIter iter;
+ gssize space_left, read, n;
+
+ dstream = GEDIT_DOCUMENT_INPUT_STREAM (stream);
+
+ 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
+gedit_document_input_stream_close (GInputStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GeditDocumentInputStream *dstream = GEDIT_DOCUMENT_INPUT_STREAM (stream);
+
+ dstream->priv->newline_added = FALSE;
+
+ if (dstream->priv->is_initialized)
+ {
+ gtk_text_buffer_delete_mark (dstream->priv->buffer, dstream->priv->pos);
+ }
+
+ return TRUE;
+}