[gtksourceview/wip/loader-saver: 71/73] GtkSourceBufferOutputStream



commit fe6cfe1cf5bec93c117dbe6b027fe17156de015d
Author: Sébastien Wilmet <swilmet gnome org>
Date:   Mon Jan 6 21:01:10 2014 +0100

    GtkSourceBufferOutputStream

 docs/reference/Makefile.am                  |    1 +
 gtksourceview/Makefile.am                   |    2 +
 gtksourceview/gtksourcebuffer-private.h     |    5 +
 gtksourceview/gtksourcebuffer.c             |   60 ++
 gtksourceview/gtksourcebufferoutputstream.c | 1117 +++++++++++++++++++++++++++
 gtksourceview/gtksourcebufferoutputstream.h |   76 ++
 gtksourceview/gtksourcefile.h               |    6 +
 gtksourceview/gtksourcetypes-private.h      |    1 +
 po/POTFILES.in                              |    1 +
 9 files changed, 1269 insertions(+), 0 deletions(-)
---
diff --git a/docs/reference/Makefile.am b/docs/reference/Makefile.am
index eaaee88..6c72ead 100644
--- a/docs/reference/Makefile.am
+++ b/docs/reference/Makefile.am
@@ -23,6 +23,7 @@ IGNORE_HFILES =                                       \
        config.h                                \
        gtksourcebuffer-private.h               \
        gtksourcebufferinputstream.h            \
+       gtksourcebufferoutputstream.h           \
        gtksourcecompletioncontainer.h          \
        gtksourcecompletionmodel.h              \
        gtksourcecompletion-private.h           \
diff --git a/gtksourceview/Makefile.am b/gtksourceview/Makefile.am
index dd3631e..937ee3f 100644
--- a/gtksourceview/Makefile.am
+++ b/gtksourceview/Makefile.am
@@ -54,6 +54,7 @@ libgtksourceview_headers =                    \
 libgtksourceview_private_headers = \
        gtksourcebuffer-private.h               \
        gtksourcebufferinputstream.h            \
+       gtksourcebufferoutputstream.h           \
        gtksourcecompletioncontainer.h          \
        gtksourcecompletionmodel.h              \
        gtksourcecompletion-private.h           \
@@ -77,6 +78,7 @@ libgtksourceview_private_headers = \
 
 libgtksourceview_private_c_files = \
        gtksourcebufferinputstream.c    \
+       gtksourcebufferoutputstream.c   \
        gtksourcecompletioncontainer.c  \
        gtksourcecompletionmodel.c      \
        gtksourcecontextengine.c        \
diff --git a/gtksourceview/gtksourcebuffer-private.h b/gtksourceview/gtksourcebuffer-private.h
index 10ac6d4..090b61d 100644
--- a/gtksourceview/gtksourcebuffer-private.h
+++ b/gtksourceview/gtksourcebuffer-private.h
@@ -50,6 +50,11 @@ G_GNUC_INTERNAL
 void                    _gtk_source_buffer_add_search_context          (GtkSourceBuffer        *buffer,
                                                                         GtkSourceSearchContext 
*search_context);
 
+G_GNUC_INTERNAL
+void                    _gtk_source_buffer_set_as_invalid_character    (GtkSourceBuffer        *buffer,
+                                                                        GtkTextIter            *start,
+                                                                        GtkTextIter            *end);
+
 G_END_DECLS
 
 #endif /* __GTK_SOURCE_BUFFER_PRIVATE_H__ */
diff --git a/gtksourceview/gtksourcebuffer.c b/gtksourceview/gtksourcebuffer.c
index 21d959e..005519f 100644
--- a/gtksourceview/gtksourcebuffer.c
+++ b/gtksourceview/gtksourcebuffer.c
@@ -164,6 +164,8 @@ struct _GtkSourceBufferPrivate
 
        GList                 *search_contexts;
 
+       GtkTextTag            *invalid_char_tag;
+
        guint                  highlight_syntax : 1;
        guint                  highlight_brackets : 1;
        guint                  constructed : 1;
@@ -2483,3 +2485,61 @@ _gtk_source_buffer_add_search_context (GtkSourceBuffer        *buffer,
                           (GWeakNotify)search_context_weak_notify_cb,
                           buffer);
 }
+
+static void
+sync_invalid_char_tag (GtkSourceBuffer *buffer,
+                      GParamSpec      *pspec,
+                      gpointer         data)
+{
+       GtkSourceStyle *style = NULL;
+
+       if (buffer->priv->style_scheme != NULL)
+       {
+               style = gtk_source_style_scheme_get_style (buffer->priv->style_scheme, "def:error");
+       }
+
+       _gtk_source_style_apply (style, buffer->priv->invalid_char_tag);
+}
+
+static void
+text_tag_set_highest_priority (GtkTextTag    *tag,
+                              GtkTextBuffer *buffer)
+{
+       GtkTextTagTable *table;
+       gint n;
+
+       table = gtk_text_buffer_get_tag_table (buffer);
+       n = gtk_text_tag_table_get_size (table);
+       gtk_text_tag_set_priority (tag, n - 1);
+}
+
+void
+_gtk_source_buffer_set_as_invalid_character (GtkSourceBuffer *buffer,
+                                            GtkTextIter     *start,
+                                            GtkTextIter     *end)
+{
+       if (buffer->priv->invalid_char_tag == NULL)
+       {
+               buffer->priv->invalid_char_tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer),
+                                                                            "invalid-char-style",
+                                                                            NULL);
+
+               sync_invalid_char_tag (buffer, NULL, NULL);
+
+               g_signal_connect (buffer,
+                                 "notify::style-scheme",
+                                 G_CALLBACK (sync_invalid_char_tag),
+                                 NULL);
+       }
+
+       /* Make sure the 'error' tag has the priority over
+        * syntax highlighting tags.
+        */
+       text_tag_set_highest_priority (buffer->priv->invalid_char_tag,
+                                      GTK_TEXT_BUFFER (buffer));
+
+       gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer),
+                                  buffer->priv->invalid_char_tag,
+                                  start,
+                                  end);
+}
diff --git a/gtksourceview/gtksourcebufferoutputstream.c b/gtksourceview/gtksourcebufferoutputstream.c
new file mode 100644
index 0000000..628a3b4
--- /dev/null
+++ b/gtksourceview/gtksourcebufferoutputstream.c
@@ -0,0 +1,1117 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* gtksourcebufferoutputstream.c
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2010 - Ignacio Casal Quinteiro
+ * Copyright (C) 2014 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * GtkSourceView 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.
+ *
+ * GtkSourceView 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 St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <string.h>
+#include <errno.h>
+#include "gtksourcebufferoutputstream.h"
+#include "gtksourcebuffer.h"
+#include "gtksourcebuffer-private.h"
+#include "gtksourceencoding.h"
+#include "gtksourceview-i18n.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 underlying code operates on a GtkTextBuffer, so
+ * there is no I/O involved and should be accessed only by the main
+ * thread.
+ */
+
+/* NOTE2: welcome to a really big headache. At the beginning this was
+ * split in several classes, one for encoding detection, another
+ * for UTF-8 conversion and another for validation. The reason this is
+ * all together is because we need specific information from all parts
+ * in other to be able to mark characters as invalid if there was some
+ * specific problem on the conversion.
+ */
+
+/* The code comes from gedit, the class was GeditDocumentOutputStream. */
+
+#if 0
+#define DEBUG(x) (x)
+#else
+#define DEBUG(x)
+#endif
+
+#define MAX_UNICHAR_LEN 6
+
+struct _GtkSourceBufferOutputStreamPrivate
+{
+       /* TODO rename */
+       GtkSourceBuffer *doc;
+       GtkTextIter pos;
+
+       gchar *buffer;
+       gsize buflen;
+
+       gchar *iconv_buffer;
+       gsize iconv_buflen;
+
+       /* Encoding detection */
+       GIConv iconv;
+       GCharsetConverter *charset_conv;
+
+       GSList *encodings;
+       GSList *current_encoding;
+
+       gint error_offset;
+       guint n_fallback_errors;
+
+       guint is_utf8 : 1;
+       guint use_first : 1;
+
+       guint is_initialized : 1;
+       guint is_closed : 1;
+
+       guint ensure_trailing_newline : 1;
+};
+
+enum
+{
+       PROP_0,
+       PROP_BUFFER,
+       PROP_ENSURE_TRAILING_NEWLINE
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GtkSourceBufferOutputStream, gtk_source_buffer_output_stream, 
G_TYPE_OUTPUT_STREAM)
+
+static gssize gtk_source_buffer_output_stream_write   (GOutputStream  *stream,
+                                                      const void     *buffer,
+                                                      gsize           count,
+                                                      GCancellable   *cancellable,
+                                                      GError        **error);
+
+static gboolean gtk_source_buffer_output_stream_close (GOutputStream  *stream,
+                                                      GCancellable   *cancellable,
+                                                      GError        **error);
+
+static gboolean gtk_source_buffer_output_stream_flush (GOutputStream  *stream,
+                                                      GCancellable   *cancellable,
+                                                      GError        **error);
+
+static void
+gtk_source_buffer_output_stream_set_property (GObject      *object,
+                                             guint         prop_id,
+                                             const GValue *value,
+                                             GParamSpec   *pspec)
+{
+       GtkSourceBufferOutputStream *stream = GTK_SOURCE_BUFFER_OUTPUT_STREAM (object);
+
+       switch (prop_id)
+       {
+               case PROP_BUFFER:
+                       stream->priv->doc = GTK_SOURCE_BUFFER (g_value_get_object (value));
+                       break;
+
+               case PROP_ENSURE_TRAILING_NEWLINE:
+                       stream->priv->ensure_trailing_newline = g_value_get_boolean (value);
+                       break;
+
+               default:
+                       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                       break;
+       }
+}
+
+static void
+gtk_source_buffer_output_stream_get_property (GObject    *object,
+                                             guint       prop_id,
+                                             GValue     *value,
+                                             GParamSpec *pspec)
+{
+       GtkSourceBufferOutputStream *stream = GTK_SOURCE_BUFFER_OUTPUT_STREAM (object);
+
+       switch (prop_id)
+       {
+               case PROP_BUFFER:
+                       g_value_set_object (value, stream->priv->doc);
+                       break;
+
+               case PROP_ENSURE_TRAILING_NEWLINE:
+                       g_value_set_boolean (value, stream->priv->ensure_trailing_newline);
+                       break;
+
+               default:
+                       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                       break;
+       }
+}
+
+static void
+gtk_source_buffer_output_stream_dispose (GObject *object)
+{
+       GtkSourceBufferOutputStream *stream = GTK_SOURCE_BUFFER_OUTPUT_STREAM (object);
+
+       g_clear_object (&stream->priv->charset_conv);
+
+       G_OBJECT_CLASS (gtk_source_buffer_output_stream_parent_class)->dispose (object);
+}
+
+static void
+gtk_source_buffer_output_stream_finalize (GObject *object)
+{
+       GtkSourceBufferOutputStream *stream = GTK_SOURCE_BUFFER_OUTPUT_STREAM (object);
+
+       g_free (stream->priv->buffer);
+       g_free (stream->priv->iconv_buffer);
+       g_slist_free (stream->priv->encodings);
+
+       G_OBJECT_CLASS (gtk_source_buffer_output_stream_parent_class)->finalize (object);
+}
+
+static void
+gtk_source_buffer_output_stream_constructed (GObject *object)
+{
+       GtkSourceBufferOutputStream *stream = GTK_SOURCE_BUFFER_OUTPUT_STREAM (object);
+
+       if (stream->priv->doc == NULL)
+       {
+               g_critical ("This should never happen, a problem happened constructing the Buffer Output 
Stream!");
+               return;
+       }
+
+       gtk_source_buffer_begin_not_undoable_action (stream->priv->doc);
+
+       gtk_text_buffer_set_text (GTK_TEXT_BUFFER (stream->priv->doc), "", 0);
+       gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (stream->priv->doc), FALSE);
+
+       gtk_source_buffer_end_not_undoable_action (GTK_SOURCE_BUFFER (stream->priv->doc));
+
+       G_OBJECT_CLASS (gtk_source_buffer_output_stream_parent_class)->constructed (object);
+}
+
+static void
+gtk_source_buffer_output_stream_class_init (GtkSourceBufferOutputStreamClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       GOutputStreamClass *stream_class = G_OUTPUT_STREAM_CLASS (klass);
+
+       object_class->get_property = gtk_source_buffer_output_stream_get_property;
+       object_class->set_property = gtk_source_buffer_output_stream_set_property;
+       object_class->dispose = gtk_source_buffer_output_stream_dispose;
+       object_class->finalize = gtk_source_buffer_output_stream_finalize;
+       object_class->constructed = gtk_source_buffer_output_stream_constructed;
+
+       stream_class->write_fn = gtk_source_buffer_output_stream_write;
+       stream_class->close_fn = gtk_source_buffer_output_stream_close;
+       stream_class->flush = gtk_source_buffer_output_stream_flush;
+
+       g_object_class_install_property (object_class,
+                                        PROP_BUFFER,
+                                        g_param_spec_object ("buffer",
+                                                             "Buffer",
+                                                             "The buffer which is written",
+                                                             GTK_SOURCE_TYPE_BUFFER,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY |
+                                                             G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (object_class,
+                                        PROP_ENSURE_TRAILING_NEWLINE,
+                                        g_param_spec_boolean ("ensure-trailing-newline",
+                                                              "Ensure Trailing Newline",
+                                                              "Ensure the file ends with a trailing newline",
+                                                              TRUE,
+                                                              G_PARAM_READWRITE |
+                                                              G_PARAM_CONSTRUCT_ONLY |
+                                                              G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gtk_source_buffer_output_stream_init (GtkSourceBufferOutputStream *stream)
+{
+       stream->priv = gtk_source_buffer_output_stream_get_instance_private (stream);
+
+       stream->priv->buffer = NULL;
+       stream->priv->buflen = 0;
+
+       stream->priv->charset_conv = NULL;
+       stream->priv->encodings = NULL;
+       stream->priv->current_encoding = NULL;
+
+       stream->priv->error_offset = -1;
+
+       stream->priv->is_initialized = FALSE;
+       stream->priv->is_closed = FALSE;
+       stream->priv->is_utf8 = FALSE;
+       stream->priv->use_first = FALSE;
+}
+
+static const GtkSourceEncoding *
+get_encoding (GtkSourceBufferOutputStream *stream)
+{
+       if (stream->priv->current_encoding == NULL)
+       {
+               stream->priv->current_encoding = stream->priv->encodings;
+       }
+       else
+       {
+               stream->priv->current_encoding = g_slist_next (stream->priv->current_encoding);
+       }
+
+       if (stream->priv->current_encoding != NULL)
+       {
+               return stream->priv->current_encoding->data;
+       }
+
+       stream->priv->use_first = TRUE;
+       stream->priv->current_encoding = stream->priv->encodings;
+
+       return stream->priv->current_encoding->data;
+}
+
+static gboolean
+try_convert (GCharsetConverter *converter,
+             const void        *inbuf,
+             gsize              inbuf_size)
+{
+       GError *err;
+       gsize bytes_read, nread;
+       gsize bytes_written, nwritten;
+       GConverterResult res;
+       gchar *out;
+       gboolean ret;
+       gsize out_size;
+
+       if (inbuf == NULL || inbuf_size == 0)
+       {
+               return FALSE;
+       }
+
+       err = NULL;
+       nread = 0;
+       nwritten = 0;
+       out_size = inbuf_size * 4;
+       out = g_malloc (out_size);
+
+       do
+       {
+               res = g_converter_convert (G_CONVERTER (converter),
+                                          (gchar *)inbuf + nread,
+                                          inbuf_size - nread,
+                                          (gchar *)out + nwritten,
+                                          out_size - nwritten,
+                                          G_CONVERTER_INPUT_AT_END,
+                                          &bytes_read,
+                                          &bytes_written,
+                                          &err);
+
+               nread += bytes_read;
+               nwritten += bytes_written;
+       } while (res != G_CONVERTER_FINISHED && res != G_CONVERTER_ERROR && err == NULL);
+
+       if (err != NULL)
+       {
+               if (err->code == G_CONVERT_ERROR_PARTIAL_INPUT)
+               {
+                       /* FIXME We can get partial input while guessing the
+                          encoding because we just take some amount of text
+                          to guess from. */
+                       ret = TRUE;
+               }
+               else
+               {
+                       ret = FALSE;
+               }
+
+               g_error_free (err);
+       }
+       else
+       {
+               ret = TRUE;
+       }
+
+       /* FIXME: Check the remainder? */
+       if (ret == TRUE && !g_utf8_validate (out, nwritten, NULL))
+       {
+               ret = FALSE;
+       }
+
+       g_free (out);
+
+       return ret;
+}
+
+static GCharsetConverter *
+guess_encoding (GtkSourceBufferOutputStream *stream,
+               const void                  *inbuf,
+               gsize                        inbuf_size)
+{
+       GCharsetConverter *conv = NULL;
+
+       if (inbuf == NULL || inbuf_size == 0)
+       {
+               stream->priv->is_utf8 = TRUE;
+               return NULL;
+       }
+
+       if (stream->priv->encodings != NULL &&
+           stream->priv->encodings->next == NULL)
+       {
+               stream->priv->use_first = TRUE;
+       }
+
+       /* We just check the first block */
+       while (TRUE)
+       {
+               const GtkSourceEncoding *enc;
+
+               g_clear_object (&conv);
+
+               /* We get an encoding from the list */
+               enc = get_encoding (stream);
+
+               /* if it is NULL we didn't guess anything */
+               if (enc == NULL)
+               {
+                       break;
+               }
+
+               DEBUG ({
+                      g_print ("trying charset: %s\n",
+                               gtk_source_encoding_get_charset (stream->priv->current_encoding->data));
+               });
+
+               if (enc == gtk_source_encoding_get_utf8 ())
+               {
+                       gsize remainder;
+                       const gchar *end;
+
+                       if (g_utf8_validate (inbuf, inbuf_size, &end) ||
+                           stream->priv->use_first)
+                       {
+                               stream->priv->is_utf8 = TRUE;
+                               break;
+                       }
+
+                       /* Check if the end is less than one char */
+                       remainder = inbuf_size - (end - (gchar *)inbuf);
+                       if (remainder < 6)
+                       {
+                               stream->priv->is_utf8 = TRUE;
+                               break;
+                       }
+
+                       continue;
+               }
+
+               conv = g_charset_converter_new ("UTF-8",
+                                               gtk_source_encoding_get_charset (enc),
+                                               NULL);
+
+               /* If we tried all encodings we use the first one */
+               if (stream->priv->use_first)
+               {
+                       break;
+               }
+
+               /* Try to convert */
+               if (try_convert (conv, inbuf, inbuf_size))
+               {
+                       break;
+               }
+       }
+
+       if (conv != NULL)
+       {
+               g_converter_reset (G_CONVERTER (conv));
+       }
+
+       return conv;
+}
+
+static GtkSourceNewlineType
+get_newline_type (GtkTextIter *end)
+{
+       GtkSourceNewlineType res;
+       GtkTextIter copy;
+       gunichar c;
+
+       copy = *end;
+       c = gtk_text_iter_get_char (&copy);
+
+       if (g_unichar_break_type (c) == G_UNICODE_BREAK_CARRIAGE_RETURN)
+       {
+               if (gtk_text_iter_forward_char (&copy) &&
+                   g_unichar_break_type (gtk_text_iter_get_char (&copy)) == G_UNICODE_BREAK_LINE_FEED)
+               {
+                       res = GTK_SOURCE_NEWLINE_TYPE_CR_LF;
+               }
+               else
+               {
+                       res = GTK_SOURCE_NEWLINE_TYPE_CR;
+               }
+       }
+       else
+       {
+               res = GTK_SOURCE_NEWLINE_TYPE_LF;
+       }
+
+       return res;
+}
+
+GOutputStream *
+gtk_source_buffer_output_stream_new (GtkSourceBuffer *buffer,
+                                    GSList          *candidate_encodings,
+                                    gboolean         ensure_trailing_newline)
+{
+       GtkSourceBufferOutputStream *stream;
+
+       stream = g_object_new (GTK_SOURCE_TYPE_BUFFER_OUTPUT_STREAM,
+                              "buffer", buffer,
+                              "ensure-trailing-newline", ensure_trailing_newline,
+                              NULL);
+
+       stream->priv->encodings = g_slist_copy (candidate_encodings);
+
+       return G_OUTPUT_STREAM (stream);
+}
+
+GtkSourceNewlineType
+gtk_source_buffer_output_stream_detect_newline_type (GtkSourceBufferOutputStream *stream)
+{
+       GtkSourceNewlineType type;
+       GtkTextIter iter;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER_OUTPUT_STREAM (stream),
+                             GTK_SOURCE_NEWLINE_TYPE_DEFAULT);
+
+       type = GTK_SOURCE_NEWLINE_TYPE_DEFAULT;
+
+       gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (stream->priv->doc),
+                                       &iter);
+
+       if (gtk_text_iter_ends_line (&iter) || gtk_text_iter_forward_to_line_end (&iter))
+       {
+               type = get_newline_type (&iter);
+       }
+
+       return type;
+}
+
+const GtkSourceEncoding *
+gtk_source_buffer_output_stream_get_guessed (GtkSourceBufferOutputStream *stream)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER_OUTPUT_STREAM (stream), NULL);
+
+       if (stream->priv->current_encoding != NULL)
+       {
+               return stream->priv->current_encoding->data;
+       }
+       else if (stream->priv->is_utf8 || !stream->priv->is_initialized)
+       {
+               /* If it is not initialized we assume that we are trying to
+                * convert the empty string.
+                */
+               return gtk_source_encoding_get_utf8 ();
+       }
+
+       return NULL;
+}
+
+guint
+gtk_source_buffer_output_stream_get_num_fallbacks (GtkSourceBufferOutputStream *stream)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER_OUTPUT_STREAM (stream), 0);
+
+       return stream->priv->n_fallback_errors;
+}
+
+static void
+apply_error_tag (GtkSourceBufferOutputStream *stream)
+{
+       GtkTextIter start;
+
+       if (stream->priv->error_offset == -1)
+       {
+               return;
+       }
+
+       gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (stream->priv->doc),
+                                           &start, stream->priv->error_offset);
+
+       _gtk_source_buffer_set_as_invalid_character (stream->priv->doc,
+                                                    &start,
+                                                    &stream->priv->pos);
+
+       stream->priv->error_offset = -1;
+}
+
+static void
+insert_fallback (GtkSourceBufferOutputStream *stream,
+                const gchar                 *buffer)
+{
+       guint8 out[4];
+       guint8 v;
+       const gchar hex[] = "0123456789ABCDEF";
+
+       /* If we are here it is because we are pointing to an invalid char so we
+        * substitute it by an hex value.
+        */
+       v = *(guint8 *)buffer;
+       out[0] = '\\';
+       out[1] = hex[(v & 0xf0) >> 4];
+       out[2] = hex[(v & 0x0f) >> 0];
+       out[3] = '\0';
+
+       gtk_text_buffer_insert (GTK_TEXT_BUFFER (stream->priv->doc),
+                               &stream->priv->pos, (const gchar *)out, 3);
+
+       ++stream->priv->n_fallback_errors;
+}
+
+static void
+validate_and_insert (GtkSourceBufferOutputStream *stream,
+                    const gchar                 *buffer,
+                    gsize                        count)
+{
+       GtkTextBuffer *text_buffer;
+       GtkTextIter *iter;
+       gsize len;
+
+       text_buffer = GTK_TEXT_BUFFER (stream->priv->doc);
+       iter = &stream->priv->pos;
+       len = count;
+
+       while (len != 0)
+       {
+               const gchar *end;
+               gboolean valid;
+               gsize nvalid;
+
+               /* validate */
+               valid = g_utf8_validate (buffer, len, &end);
+               nvalid = end - buffer;
+
+               /* Note: this is a workaround for a 'bug' in GtkTextBuffer where
+                  inserting first a \r and then in a second insert, a \n,
+                  will result in two lines being added instead of a single
+                  one */
+
+               if (valid)
+               {
+                       gchar *ptr;
+
+                       ptr = g_utf8_find_prev_char (buffer, buffer + len);
+
+                       if (ptr && *ptr == '\r' && ptr - buffer == len - 1)
+                       {
+                               stream->priv->buffer = g_new (gchar, 1);
+                               stream->priv->buffer[0] = '\r';
+                               stream->priv->buflen = 1;
+
+                               /* Decrease also the len so in the check
+                                  nvalid == len we get out of this method */
+                               --nvalid;
+                               --len;
+                       }
+               }
+
+               /* if we've got any valid char we must tag the invalid chars */
+               if (nvalid > 0)
+               {
+                       apply_error_tag (stream);
+               }
+
+               gtk_text_buffer_insert (text_buffer, iter, buffer, nvalid);
+
+               /* If we inserted all return */
+               if (nvalid == len)
+               {
+                       break;
+               }
+
+               buffer += nvalid;
+               len = len - nvalid;
+
+               if ((len < MAX_UNICHAR_LEN) &&
+                   (g_utf8_get_char_validated (buffer, len) == (gunichar)-2))
+               {
+                       stream->priv->buffer = g_strndup (end, len);
+                       stream->priv->buflen = len;
+
+                       break;
+               }
+
+               /* we need the start of the chunk of invalid chars */
+               if (stream->priv->error_offset == -1)
+               {
+                       stream->priv->error_offset = gtk_text_iter_get_offset (&stream->priv->pos);
+               }
+
+               insert_fallback (stream, buffer);
+               ++buffer;
+               --len;
+       }
+}
+
+/* If the last char is a newline, remove it from the buffer (otherwise
+ * GtkTextView shows it as an empty line).
+ * See https://bugzilla.gnome.org/show_bug.cgi?id=324942
+ */
+static void
+remove_ending_newline (GtkSourceBufferOutputStream *stream)
+{
+       GtkTextIter end;
+       GtkTextIter start;
+
+       gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (stream->priv->doc), &end);
+       start = end;
+
+       gtk_text_iter_set_line_offset (&start, 0);
+
+       if (gtk_text_iter_ends_line (&start) &&
+           gtk_text_iter_backward_line (&start))
+       {
+               if (!gtk_text_iter_ends_line (&start))
+               {
+                       gtk_text_iter_forward_to_line_end (&start);
+               }
+
+               /* Delete the empty line which is from 'start' to 'end' */
+               gtk_text_buffer_delete (GTK_TEXT_BUFFER (stream->priv->doc),
+                                       &start,
+                                       &end);
+       }
+}
+
+static void
+end_append_text_to_document (GtkSourceBufferOutputStream *stream)
+{
+       if (stream->priv->ensure_trailing_newline)
+       {
+               remove_ending_newline (stream);
+       }
+
+       gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (stream->priv->doc),
+                                     FALSE);
+
+       gtk_source_buffer_end_not_undoable_action (GTK_SOURCE_BUFFER (stream->priv->doc));
+}
+
+static gboolean
+convert_text (GtkSourceBufferOutputStream  *stream,
+             const gchar                  *inbuf,
+             gsize                         inbuf_len,
+             gchar                       **outbuf,
+             gsize                        *outbuf_len,
+             GError                      **error)
+{
+       gchar *out, *dest;
+       gsize in_left, out_left, outbuf_size, res;
+       gint errsv;
+       gboolean done, have_error;
+
+       in_left = inbuf_len;
+       /* set an arbitrary length if inbuf_len is 0, this is needed to flush
+          the iconv data */
+       outbuf_size = (inbuf_len > 0) ? inbuf_len : 100;
+
+       out_left = outbuf_size;
+       out = dest = g_malloc (outbuf_size);
+
+       done = FALSE;
+       have_error = FALSE;
+
+       while (!done && !have_error)
+       {
+               /* If we reached here is because we need to convert the text,
+                  so we convert it using iconv.
+                  See that if inbuf is NULL the data will be flushed */
+               res = g_iconv (stream->priv->iconv,
+                              (gchar **)&inbuf, &in_left,
+                              &out, &out_left);
+
+               /* something went wrong */
+               if (res == (gsize)-1)
+               {
+                       errsv = errno;
+
+                       switch (errsv)
+                       {
+                               case EINVAL:
+                                       /* Incomplete text, do not report an error */
+                                       stream->priv->iconv_buffer = g_strndup (inbuf, in_left);
+                                       stream->priv->iconv_buflen = in_left;
+                                       done = TRUE;
+                                       break;
+
+                               case E2BIG:
+                                       {
+                                               /* allocate more space */
+                                               gsize used = out - dest;
+
+                                               outbuf_size *= 2;
+                                               dest = g_realloc (dest, outbuf_size);
+
+                                               out = dest + used;
+                                               out_left = outbuf_size - used;
+                                       }
+                                       break;
+
+                               case EILSEQ:
+                                       /* TODO: we should escape this text.*/
+                                       g_set_error_literal (error, G_CONVERT_ERROR,
+                                                            G_CONVERT_ERROR_ILLEGAL_SEQUENCE,
+                                                            _("Invalid byte sequence in conversion input"));
+                                       have_error = TRUE;
+                                       break;
+
+                               default:
+                                       g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_FAILED,
+                                                    _("Error during conversion: %s"),
+                                                    g_strerror (errsv));
+                                       have_error = TRUE;
+                                       break;
+                       }
+               }
+               else
+               {
+                       done = TRUE;
+               }
+       }
+
+       if (have_error)
+       {
+               g_free (dest);
+               *outbuf = NULL;
+               *outbuf_len = 0;
+
+               return FALSE;
+       }
+
+       *outbuf = dest;
+       *outbuf_len = out - dest;
+
+       return TRUE;
+}
+
+static gssize
+gtk_source_buffer_output_stream_write (GOutputStream  *stream,
+                                      const void     *buffer,
+                                      gsize           count,
+                                      GCancellable   *cancellable,
+                                      GError        **error)
+{
+       GtkSourceBufferOutputStream *ostream;
+       gchar *text;
+       gsize len;
+       gboolean freetext = FALSE;
+
+       if (g_cancellable_set_error_if_cancelled (cancellable, error))
+       {
+               return -1;
+       }
+
+       ostream = GTK_SOURCE_BUFFER_OUTPUT_STREAM (stream);
+
+       if (!ostream->priv->is_initialized)
+       {
+               ostream->priv->charset_conv = guess_encoding (ostream, buffer, count);
+
+               /* If we still have the previous case is that we didn't guess
+                  anything */
+               if (ostream->priv->charset_conv == NULL &&
+                   !ostream->priv->is_utf8)
+               {
+                       g_set_error_literal (error, GTK_SOURCE_FILE_ERROR,
+                                            GTK_SOURCE_FILE_ERROR_ENCODING_AUTO_DETECTION_FAILED,
+                                            _("It is not possible to detect the encoding automatically"));
+
+                       return -1;
+               }
+
+               /* Do not initialize iconv if we are not going to convert anything */
+               if (!ostream->priv->is_utf8)
+               {
+                       gchar *from_charset;
+
+                       /* Initialize iconv */
+                       g_object_get (G_OBJECT (ostream->priv->charset_conv),
+                                     "from-charset", &from_charset,
+                                     NULL);
+
+                       ostream->priv->iconv = g_iconv_open ("UTF-8", from_charset);
+
+                       if (ostream->priv->iconv == (GIConv)-1)
+                       {
+                               if (errno == EINVAL)
+                               {
+                                       g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                                                    _("Conversion from character set '%s' to 'UTF-8' is not 
supported"),
+                                                    from_charset);
+                               }
+                               else
+                               {
+                                       g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                                                    _("Could not open converter from '%s' to 'UTF-8'"),
+                                                    from_charset);
+                               }
+
+                               g_free (from_charset);
+                               g_clear_object (&ostream->priv->charset_conv);
+
+                               return -1;
+                       }
+
+                       g_free (from_charset);
+               }
+
+               /* Init the undoable action */
+               gtk_source_buffer_begin_not_undoable_action (GTK_SOURCE_BUFFER (ostream->priv->doc));
+
+               gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (ostream->priv->doc),
+                                               &ostream->priv->pos);
+
+               ostream->priv->is_initialized = TRUE;
+       }
+
+       if (ostream->priv->buflen > 0)
+       {
+               len = ostream->priv->buflen + count;
+               text = g_malloc (len + 1);
+
+               memcpy (text, ostream->priv->buffer, ostream->priv->buflen);
+               memcpy (text + ostream->priv->buflen, buffer, count);
+
+               text[len] = '\0';
+
+               g_free (ostream->priv->buffer);
+
+               ostream->priv->buffer = NULL;
+               ostream->priv->buflen = 0;
+
+               freetext = TRUE;
+       }
+       else
+       {
+               text = (gchar *) buffer;
+               len = count;
+       }
+
+       if (!ostream->priv->is_utf8)
+       {
+               gchar *outbuf;
+               gsize outbuf_len;
+
+               /* check if iconv was correctly initializated, this shouldn't
+                  happen but better be safe */
+               if (ostream->priv->iconv == NULL)
+               {
+                       g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED,
+                                            _("Invalid object, not initialized"));
+
+                       if (freetext)
+                       {
+                               g_free (text);
+                       }
+
+                       return -1;
+               }
+
+               /* manage the previous conversion buffer */
+               if (ostream->priv->iconv_buflen > 0)
+               {
+                       gchar *text2;
+                       gsize len2;
+
+                       len2 = len + ostream->priv->iconv_buflen;
+                       text2 = g_malloc (len2 + 1);
+
+                       memcpy (text2, ostream->priv->iconv_buffer, ostream->priv->iconv_buflen);
+                       memcpy (text2 + ostream->priv->iconv_buflen, text, len);
+
+                       text2[len2] = '\0';
+
+                       if (freetext)
+                       {
+                               g_free (text);
+                       }
+
+                       text = text2;
+                       len = len2;
+
+                       g_free (ostream->priv->iconv_buffer);
+
+                       ostream->priv->iconv_buffer = NULL;
+                       ostream->priv->iconv_buflen = 0;
+
+                       freetext = TRUE;
+               }
+
+               if (!convert_text (ostream, text, len, &outbuf, &outbuf_len, error))
+               {
+                       if (freetext)
+                       {
+                               g_free (text);
+                       }
+
+                       return -1;
+               }
+
+               if (freetext)
+               {
+                       g_free (text);
+               }
+
+               /* set the converted text as the text to validate */
+               text = outbuf;
+               len = outbuf_len;
+       }
+
+       validate_and_insert (ostream, text, len);
+
+       if (freetext)
+       {
+               g_free (text);
+       }
+
+       return count;
+}
+
+static gboolean
+gtk_source_buffer_output_stream_flush (GOutputStream  *stream,
+                                      GCancellable   *cancellable,
+                                      GError        **error)
+{
+       GtkSourceBufferOutputStream *ostream;
+
+       ostream = GTK_SOURCE_BUFFER_OUTPUT_STREAM (stream);
+
+       if (ostream->priv->is_closed)
+       {
+               return TRUE;
+       }
+
+       /* if we have converted something flush residual data, validate and insert */
+       if (ostream->priv->iconv != NULL)
+       {
+               gchar *outbuf;
+               gsize outbuf_len;
+
+               if (convert_text (ostream, NULL, 0, &outbuf, &outbuf_len, error))
+               {
+                       validate_and_insert (ostream, outbuf, outbuf_len);
+                       g_free (outbuf);
+               }
+               else
+               {
+                       return FALSE;
+               }
+       }
+
+       if (ostream->priv->buflen > 0 && *ostream->priv->buffer != '\r')
+       {
+               /* If we reached here is because the last insertion was a half
+                  correct char, which has to be inserted as fallback */
+               gchar *text;
+
+               if (ostream->priv->error_offset == -1)
+               {
+                       ostream->priv->error_offset = gtk_text_iter_get_offset (&ostream->priv->pos);
+               }
+
+               text = ostream->priv->buffer;
+               while (ostream->priv->buflen != 0)
+               {
+                       insert_fallback (ostream, text);
+                       ++text;
+                       --ostream->priv->buflen;
+               }
+
+               g_free (ostream->priv->buffer);
+               ostream->priv->buffer = NULL;
+       }
+       else if (ostream->priv->buflen == 1 && *ostream->priv->buffer == '\r')
+       {
+               /* The previous chars can be invalid */
+               apply_error_tag (ostream);
+
+               /* See special case above, flush this */
+               gtk_text_buffer_insert (GTK_TEXT_BUFFER (ostream->priv->doc),
+                                       &ostream->priv->pos,
+                                       "\r",
+                                       1);
+
+               g_free (ostream->priv->buffer);
+               ostream->priv->buffer = NULL;
+               ostream->priv->buflen = 0;
+       }
+
+       if (ostream->priv->iconv_buflen > 0 )
+       {
+               /* If we reached here is because the last insertion was a half
+                  correct char, which has to be inserted as fallback */
+               gchar *text;
+
+               if (ostream->priv->error_offset == -1)
+               {
+                       ostream->priv->error_offset = gtk_text_iter_get_offset (&ostream->priv->pos);
+               }
+
+               text = ostream->priv->iconv_buffer;
+               while (ostream->priv->iconv_buflen != 0)
+               {
+                       insert_fallback (ostream, text);
+                       ++text;
+                       --ostream->priv->iconv_buflen;
+               }
+
+               g_free (ostream->priv->iconv_buffer);
+               ostream->priv->iconv_buffer = NULL;
+       }
+
+       apply_error_tag (ostream);
+
+       return TRUE;
+}
+
+static gboolean
+gtk_source_buffer_output_stream_close (GOutputStream  *stream,
+                                      GCancellable   *cancellable,
+                                      GError        **error)
+{
+       GtkSourceBufferOutputStream *ostream = GTK_SOURCE_BUFFER_OUTPUT_STREAM (stream);
+
+       if (!ostream->priv->is_closed && ostream->priv->is_initialized)
+       {
+               end_append_text_to_document (ostream);
+
+               if (ostream->priv->iconv != NULL)
+               {
+                       g_iconv_close (ostream->priv->iconv);
+               }
+
+               ostream->priv->is_closed = TRUE;
+       }
+
+       if (ostream->priv->buflen > 0 || ostream->priv->iconv_buflen > 0)
+       {
+               g_set_error (error,
+                            G_IO_ERROR,
+                            G_IO_ERROR_INVALID_DATA,
+                            _("Incomplete UTF-8 sequence in input"));
+
+               return FALSE;
+       }
+
+       return TRUE;
+}
diff --git a/gtksourceview/gtksourcebufferoutputstream.h b/gtksourceview/gtksourcebufferoutputstream.h
new file mode 100644
index 0000000..13228d0
--- /dev/null
+++ b/gtksourceview/gtksourcebufferoutputstream.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* gtksourcebufferoutputstream.h
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2010 - Ignacio Casal Quinteiro
+ * Copyright (C) 2014 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * GtkSourceView 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.
+ *
+ * GtkSourceView 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 St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __GTK_SOURCE_BUFFER_OUTPUT_STREAM_H__
+#define __GTK_SOURCE_BUFFER_OUTPUT_STREAM_H__
+
+#include <gtk/gtk.h>
+#include "gtksourcetypes.h"
+#include "gtksourcetypes-private.h"
+#include "gtksourcefile.h"
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_BUFFER_OUTPUT_STREAM           (gtk_source_buffer_output_stream_get_type ())
+#define GTK_SOURCE_BUFFER_OUTPUT_STREAM(obj)           (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
GTK_SOURCE_TYPE_BUFFER_OUTPUT_STREAM, GtkSourceBufferOutputStream))
+#define GTK_SOURCE_BUFFER_OUTPUT_STREAM_CLASS(klass)   (G_TYPE_CHECK_CLASS_CAST ((klass), 
GTK_SOURCE_TYPE_BUFFER_OUTPUT_STREAM, GtkSourceBufferOutputStreamClass))
+#define GTK_SOURCE_IS_BUFFER_OUTPUT_STREAM(obj)                (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
GTK_SOURCE_TYPE_BUFFER_OUTPUT_STREAM))
+#define GTK_SOURCE_IS_BUFFER_OUTPUT_STREAM_CLASS(klass)        (G_TYPE_CHECK_CLASS_TYPE ((klass), 
GTK_SOURCE_TYPE_BUFFER_OUTPUT_STREAM))
+#define GTK_SOURCE_BUFFER_OUTPUT_STREAM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), 
GTK_SOURCE_TYPE_BUFFER_OUTPUT_STREAM, GtkSourceBufferOutputStreamClass))
+
+typedef struct _GtkSourceBufferOutputStreamClass       GtkSourceBufferOutputStreamClass;
+typedef struct _GtkSourceBufferOutputStreamPrivate     GtkSourceBufferOutputStreamPrivate;
+
+struct _GtkSourceBufferOutputStream
+{
+       GOutputStream parent;
+
+       GtkSourceBufferOutputStreamPrivate *priv;
+};
+
+struct _GtkSourceBufferOutputStreamClass
+{
+       GOutputStreamClass parent_class;
+};
+
+G_GNUC_INTERNAL
+GType                   gtk_source_buffer_output_stream_get_type       (void) G_GNUC_CONST;
+
+G_GNUC_INTERNAL
+GOutputStream          *gtk_source_buffer_output_stream_new            (GtkSourceBuffer             *buffer,
+                                                                        GSList                      
*candidate_encodings,
+                                                                        gboolean                     
ensure_trailing_newline);
+
+G_GNUC_INTERNAL
+GtkSourceNewlineType    gtk_source_buffer_output_stream_detect_newline_type
+                                                                       (GtkSourceBufferOutputStream *stream);
+
+G_GNUC_INTERNAL
+const GtkSourceEncoding        *gtk_source_buffer_output_stream_get_guessed    (GtkSourceBufferOutputStream 
*stream);
+
+G_GNUC_INTERNAL
+guint                   gtk_source_buffer_output_stream_get_num_fallbacks
+                                                                       (GtkSourceBufferOutputStream *stream);
+
+G_END_DECLS
+
+#endif /* __GTK_SOURCE_BUFFER_OUTPUT_STREAM_H__ */
diff --git a/gtksourceview/gtksourcefile.h b/gtksourceview/gtksourcefile.h
index dc4ccbe..206f458 100644
--- a/gtksourceview/gtksourcefile.h
+++ b/gtksourceview/gtksourcefile.h
@@ -53,6 +53,12 @@ typedef enum
        GTK_SOURCE_NEWLINE_TYPE_CR_LF
 } GtkSourceNewlineType;
 
+#ifdef G_OS_WIN32
+#define GTK_SOURCE_NEWLINE_TYPE_DEFAULT GTK_SOURCE_NEWLINE_TYPE_CR_LF
+#else
+#define GTK_SOURCE_NEWLINE_TYPE_DEFAULT GTK_SOURCE_NEWLINE_TYPE_LF
+#endif
+
 /* NOTE: when adding a new compression type, make sure to update:
  *   1) The buffer loader to support it
  *   2) gedit_document_compression_type_for_display
diff --git a/gtksourceview/gtksourcetypes-private.h b/gtksourceview/gtksourcetypes-private.h
index cc38d97..9107afe 100644
--- a/gtksourceview/gtksourcetypes-private.h
+++ b/gtksourceview/gtksourcetypes-private.h
@@ -27,6 +27,7 @@
 G_BEGIN_DECLS
 
 typedef struct _GtkSourceBufferInputStream     GtkSourceBufferInputStream;
+typedef struct _GtkSourceBufferOutputStream    GtkSourceBufferOutputStream;
 typedef struct _GtkSourceCompletionContainer   GtkSourceCompletionContainer;
 typedef struct _GtkSourceCompletionModel       GtkSourceCompletionModel;
 typedef struct _GtkSourceContextEngine         GtkSourceContextEngine;
diff --git a/po/POTFILES.in b/po/POTFILES.in
index f5739d1..c3a8d5a 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -121,6 +121,7 @@ data/styles/tango.xml
 gtksourceview/completion-providers/words/gtksourcecompletionwords.c
 gtksourceview/gtksourcebuffer.c
 gtksourceview/gtksourcebufferinputstream.c
+gtksourceview/gtksourcebufferoutputstream.c
 gtksourceview/gtksourcecompletion.c
 gtksourceview/gtksourcecompletioncontainer.c
 gtksourceview/gtksourcecompletioncontext.c



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]