[gtksourceview/wip/loader-saver: 3/4] File loading and saving



commit fc47ed648b3862eee81168e192aa69ae53f18469
Author: Sébastien Wilmet <swilmet gnome org>
Date:   Fri Dec 6 19:05:33 2013 +0100

    File loading and saving
    
    New public classes: GtkSourceEncoding, GtkSourceFile,
    GtkSourceFileLoader and GtkSourceFileSaver.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=721016

 docs/reference/Makefile.am                    |    2 +
 docs/reference/gtksourceview-3.0-sections.txt |  116 +++
 docs/reference/gtksourceview-docs.xml         |    8 +
 gtksourceview/Makefile.am                     |   12 +
 gtksourceview/gtksource.h                     |    4 +
 gtksourceview/gtksourcebuffer-private.h       |    8 +
 gtksourceview/gtksourcebuffer.c               |  165 +++-
 gtksourceview/gtksourcebuffer.h               |    6 +
 gtksourceview/gtksourcebufferinputstream.c    |  515 ++++++++++
 gtksourceview/gtksourcebufferinputstream.h    |   72 ++
 gtksourceview/gtksourcebufferoutputstream.c   | 1141 +++++++++++++++++++++
 gtksourceview/gtksourcebufferoutputstream.h   |   77 ++
 gtksourceview/gtksourceencoding.c             |  589 +++++++++++
 gtksourceview/gtksourceencoding.h             |   62 ++
 gtksourceview/gtksourcefile.c                 |  464 +++++++++
 gtksourceview/gtksourcefile.h                 |  115 +++
 gtksourceview/gtksourcefileloader.c           | 1112 ++++++++++++++++++++
 gtksourceview/gtksourcefileloader.h           |  123 +++
 gtksourceview/gtksourcefilesaver.c            | 1345 +++++++++++++++++++++++++
 gtksourceview/gtksourcefilesaver.h            |  144 +++
 gtksourceview/gtksourcetypes-private.h        |    2 +
 gtksourceview/gtksourcetypes.h                |   50 +-
 po/POTFILES.in                                |    6 +
 tests/Makefile.am                             |   31 +
 tests/setup-file-saver.sh                     |   27 +
 tests/test-buffer-input-stream.c              |  154 +++
 tests/test-buffer-output-stream.c             |  425 ++++++++
 tests/test-file-loader.c                      |  236 +++++
 tests/test-file-saver.c                       |  758 ++++++++++++++
 29 files changed, 7766 insertions(+), 3 deletions(-)
---
diff --git a/docs/reference/Makefile.am b/docs/reference/Makefile.am
index 1987d7f..effba76 100644
--- a/docs/reference/Makefile.am
+++ b/docs/reference/Makefile.am
@@ -22,6 +22,8 @@ CFILE_GLOB = $(top_srcdir)/gtksourceview/*.c
 IGNORE_HFILES =                                        \
        config.h                                \
        gtksourcebuffer-private.h               \
+       gtksourcebufferinputstream.h            \
+       gtksourcebufferoutputstream.h           \
        gtksourcecompletioncontainer.h          \
        gtksourcecompletionmodel.h              \
        gtksourcecompletion-private.h           \
diff --git a/docs/reference/gtksourceview-3.0-sections.txt b/docs/reference/gtksourceview-3.0-sections.txt
index 0618e46..c016477 100644
--- a/docs/reference/gtksourceview-3.0-sections.txt
+++ b/docs/reference/gtksourceview-3.0-sections.txt
@@ -39,6 +39,9 @@ gtk_source_buffer_begin_not_undoable_action
 gtk_source_buffer_end_not_undoable_action
 gtk_source_buffer_get_undo_manager
 gtk_source_buffer_set_undo_manager
+<SUBSECTION>
+gtk_source_buffer_set_implicit_trailing_newline
+gtk_source_buffer_get_implicit_trailing_newline
 <SUBSECTION Standard>
 GtkSourceBufferClass
 GTK_SOURCE_IS_BUFFER
@@ -207,6 +210,119 @@ gtk_source_completion_words_get_type
 </SECTION>
 
 <SECTION>
+<FILE>encoding</FILE>
+<TITLE>GtkSourceEncoding</TITLE>
+GtkSourceEncoding
+<SUBSECTION>
+gtk_source_encoding_get_utf8
+gtk_source_encoding_get_current
+gtk_source_encoding_get_from_charset
+gtk_source_encoding_get_from_index
+gtk_source_encoding_to_string
+gtk_source_encoding_get_name
+gtk_source_encoding_get_charset
+gtk_source_encoding_copy
+gtk_source_encoding_free
+<SUBSECTION Standard>
+GTK_SOURCE_TYPE_ENCODING
+gtk_source_encoding_get_type
+</SECTION>
+
+<SECTION>
+<FILE>file</FILE>
+<TITLE>GtkSourceFile</TITLE>
+GtkSourceFile
+GtkSourceNewlineType
+GTK_SOURCE_NEWLINE_TYPE_DEFAULT
+GtkSourceCompressionType
+GtkSourceMountOperationFactory
+<SUBSECTION>
+gtk_source_file_new
+gtk_source_file_get_location
+gtk_source_file_set_location
+gtk_source_file_get_encoding
+gtk_source_file_get_newline_type
+gtk_source_file_get_compression_type
+gtk_source_file_set_mount_operation_factory
+<SUBSECTION Standard>
+GTK_SOURCE_FILE
+GTK_SOURCE_FILE_CLASS
+GTK_SOURCE_FILE_GET_CLASS
+GTK_SOURCE_IS_FILE
+GTK_SOURCE_IS_FILE_CLASS
+GTK_SOURCE_TYPE_FILE
+GtkSourceFileClass
+GtkSourceFilePrivate
+gtk_source_file_get_type
+</SECTION>
+
+<SECTION>
+<FILE>fileloader</FILE>
+<TITLE>GtkSourceFileLoader</TITLE>
+GtkSourceFileLoader
+GTK_SOURCE_FILE_LOADER_ERROR
+GtkSourceFileLoaderError
+<SUBSECTION>
+gtk_source_file_loader_new
+gtk_source_file_loader_new_from_stream
+gtk_source_file_loader_set_candidate_encodings
+gtk_source_file_loader_get_buffer
+gtk_source_file_loader_get_file
+gtk_source_file_loader_get_location
+gtk_source_file_loader_get_input_stream
+gtk_source_file_loader_load_async
+gtk_source_file_loader_load_finish
+gtk_source_file_loader_get_encoding
+gtk_source_file_loader_get_newline_type
+gtk_source_file_loader_get_compression_type
+<SUBSECTION Standard>
+GTK_SOURCE_FILE_LOADER
+GTK_SOURCE_FILE_LOADER_CLASS
+GTK_SOURCE_FILE_LOADER_GET_CLASS
+GTK_SOURCE_IS_FILE_LOADER
+GTK_SOURCE_IS_FILE_LOADER_CLASS
+GTK_SOURCE_TYPE_FILE_LOADER
+GtkSourceFileLoaderPrivate
+gtk_source_file_loader_get_type
+gtk_source_file_loader_error_quark
+</SECTION>
+
+<SECTION>
+<FILE>filesaver</FILE>
+<TITLE>GtkSourceFileSaver</TITLE>
+GtkSourceFileSaver
+GTK_SOURCE_FILE_SAVER_ERROR
+GtkSourceFileSaverError
+GtkSourceFileSaverFlags
+<SUBSECTION>
+gtk_source_file_saver_new
+gtk_source_file_saver_get_buffer
+gtk_source_file_saver_get_file
+gtk_source_file_saver_get_location
+gtk_source_file_saver_set_encoding
+gtk_source_file_saver_get_encoding
+gtk_source_file_saver_set_newline_type
+gtk_source_file_saver_get_newline_type
+gtk_source_file_saver_set_compression_type
+gtk_source_file_saver_get_compression_type
+gtk_source_file_saver_set_flags
+gtk_source_file_saver_get_flags
+gtk_source_file_saver_save_async
+gtk_source_file_saver_save_finish
+<SUBSECTION Standard>
+GTK_SOURCE_FILE_SAVER
+GTK_SOURCE_FILE_SAVER_CLASS
+GTK_SOURCE_FILE_SAVER_GET_CLASS
+GTK_SOURCE_IS_FILE_SAVER
+GTK_SOURCE_IS_FILE_SAVER_CLASS
+GTK_SOURCE_TYPE_FILE_SAVER
+GtkSourceFileSaverClass
+GtkSourceFileSaverPrivate
+gtk_source_file_saver_get_type
+gtk_source_file_saver_error_quark
+</SECTION>
+
+<SECTION>
 <FILE>gutter</FILE>
 <TITLE>GtkSourceGutter</TITLE>
 GtkSourceGutter
diff --git a/docs/reference/gtksourceview-docs.xml b/docs/reference/gtksourceview-docs.xml
index 658ada7..9b0b58c 100644
--- a/docs/reference/gtksourceview-docs.xml
+++ b/docs/reference/gtksourceview-docs.xml
@@ -24,6 +24,10 @@
     <xi:include href="xml/completionproposal.xml"/>
     <xi:include href="xml/completionprovider.xml"/>
     <xi:include href="xml/completionwords.xml"/>
+    <xi:include href="xml/encoding.xml"/>
+    <xi:include href="xml/file.xml"/>
+    <xi:include href="xml/fileloader.xml"/>
+    <xi:include href="xml/filesaver.xml"/>
     <xi:include href="xml/gutter.xml"/>
     <xi:include href="xml/gutterrenderer.xml"/>
     <xi:include href="xml/gutterrendererpixbuf.xml"/>
@@ -80,6 +84,10 @@
     <title>Index of new symbols in 3.12</title>
     <xi:include href="xml/api-index-3.12.xml"><xi:fallback /></xi:include>
   </index>
+  <index id="api-index-3-14" role="3.14">
+    <title>Index of new symbols in 3.14</title>
+    <xi:include href="xml/api-index-3.14.xml"><xi:fallback /></xi:include>
+  </index>
 
   <xi:include href="xml/annotation-glossary.xml"><xi:fallback /></xi:include>
 
diff --git a/gtksourceview/Makefile.am b/gtksourceview/Makefile.am
index 7ab2744..c60ecf8 100644
--- a/gtksourceview/Makefile.am
+++ b/gtksourceview/Makefile.am
@@ -30,6 +30,10 @@ libgtksourceview_headers =                   \
        gtksourcecompletionitem.h               \
        gtksourcecompletionproposal.h           \
        gtksourcecompletionprovider.h           \
+       gtksourceencoding.h                     \
+       gtksourcefile.h                         \
+       gtksourcefileloader.h                   \
+       gtksourcefilesaver.h                    \
        gtksourcegutter.h                       \
        gtksourcegutterrenderer.h               \
        gtksourcegutterrendererpixbuf.h         \
@@ -51,6 +55,8 @@ libgtksourceview_headers =                    \
 
 libgtksourceview_private_headers =             \
        gtksourcebuffer-private.h               \
+       gtksourcebufferinputstream.h            \
+       gtksourcebufferoutputstream.h           \
        gtksourcecompletioncontainer.h          \
        gtksourcecompletionmodel.h              \
        gtksourcecompletion-private.h           \
@@ -72,6 +78,8 @@ libgtksourceview_private_headers =            \
        gtktextregion.h
 
 libgtksourceview_private_c_files =     \
+       gtksourcebufferinputstream.c    \
+       gtksourcebufferoutputstream.c   \
        gtksourcecompletioncontainer.c  \
        gtksourcecompletionmodel.c      \
        gtksourcecontextengine.c        \
@@ -97,6 +105,10 @@ libgtksourceview_c_files =          \
        gtksourcecompletionitem.c       \
        gtksourcecompletionproposal.c   \
        gtksourcecompletionprovider.c   \
+       gtksourceencoding.c             \
+       gtksourcefile.c                 \
+       gtksourcefileloader.c           \
+       gtksourcefilesaver.c            \
        gtksourcegutter.c               \
        gtksourcegutterrenderer.c       \
        gtksourcegutterrendererpixbuf.c \
diff --git a/gtksourceview/gtksource.h b/gtksourceview/gtksource.h
index 1c7bd79..0903003 100644
--- a/gtksourceview/gtksource.h
+++ b/gtksourceview/gtksource.h
@@ -28,6 +28,10 @@
 #include <gtksourceview/gtksourcecompletionitem.h>
 #include <gtksourceview/gtksourcecompletionproposal.h>
 #include <gtksourceview/gtksourcecompletionprovider.h>
+#include <gtksourceview/gtksourceencoding.h>
+#include <gtksourceview/gtksourcefile.h>
+#include <gtksourceview/gtksourcefileloader.h>
+#include <gtksourceview/gtksourcefilesaver.h>
 #include <gtksourceview/gtksourcegutter.h>
 #include <gtksourceview/gtksourcegutterrenderer.h>
 #include <gtksourceview/gtksourcegutterrenderertext.h>
diff --git a/gtksourceview/gtksourcebuffer-private.h b/gtksourceview/gtksourcebuffer-private.h
index 10ac6d4..9f1dae5 100644
--- a/gtksourceview/gtksourcebuffer-private.h
+++ b/gtksourceview/gtksourcebuffer-private.h
@@ -50,6 +50,14 @@ 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,
+                                                                        const GtkTextIter      *start,
+                                                                        const GtkTextIter      *end);
+
+G_GNUC_INTERNAL
+gboolean                _gtk_source_buffer_has_invalid_chars           (GtkSourceBuffer        *buffer);
+
 G_END_DECLS
 
 #endif /* __GTK_SOURCE_BUFFER_PRIVATE_H__ */
diff --git a/gtksourceview/gtksourcebuffer.c b/gtksourceview/gtksourcebuffer.c
index d9b5886..2c4b98c 100644
--- a/gtksourceview/gtksourcebuffer.c
+++ b/gtksourceview/gtksourcebuffer.c
@@ -140,7 +140,8 @@ enum {
        PROP_MAX_UNDO_LEVELS,
        PROP_LANGUAGE,
        PROP_STYLE_SCHEME,
-       PROP_UNDO_MANAGER
+       PROP_UNDO_MANAGER,
+       PROP_IMPLICIT_TRAILING_NEWLINE
 };
 
 struct _GtkSourceBufferPrivate
@@ -164,10 +165,13 @@ struct _GtkSourceBufferPrivate
 
        GList                 *search_contexts;
 
+       GtkTextTag            *invalid_char_tag;
+
        guint                  highlight_syntax : 1;
        guint                  highlight_brackets : 1;
        guint                  constructed : 1;
        guint                  allow_bracket_match : 1;
+       guint                  implicit_trailing_newline : 1;
 };
 
 G_DEFINE_TYPE_WITH_PRIVATE (GtkSourceBuffer, gtk_source_buffer, GTK_TYPE_TEXT_BUFFER)
@@ -357,6 +361,22 @@ gtk_source_buffer_class_init (GtkSourceBufferClass *klass)
                                                              GTK_SOURCE_TYPE_UNDO_MANAGER,
                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
 
+       /**
+        * GtkSourceBuffer:implicit-trailing-newline:
+        *
+        * Whether the buffer has an implicit trailing newline.
+        *
+        * Since: 3.14
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_IMPLICIT_TRAILING_NEWLINE,
+                                        g_param_spec_boolean ("implicit-trailing-newline",
+                                                              _("Implicit trailing newline"),
+                                                              "",
+                                                              TRUE,
+                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+                                                              G_PARAM_STATIC_STRINGS));
+
        param_types[0] = GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE;
        param_types[1] = GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE;
 
@@ -627,6 +647,11 @@ gtk_source_buffer_set_property (GObject      *object,
                                                            g_value_get_object (value));
                        break;
 
+               case PROP_IMPLICIT_TRAILING_NEWLINE:
+                       gtk_source_buffer_set_implicit_trailing_newline (source_buffer,
+                                                                        g_value_get_boolean (value));
+                       break;
+
                default:
                        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                        break;
@@ -682,6 +707,10 @@ gtk_source_buffer_get_property (GObject    *object,
                        g_value_set_object (value, source_buffer->priv->undo_manager);
                        break;
 
+               case PROP_IMPLICIT_TRAILING_NEWLINE:
+                       g_value_set_boolean (value, source_buffer->priv->implicit_trailing_newline);
+                       break;
+
                default:
                        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                        break;
@@ -2483,3 +2512,137 @@ _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,
+                                            const GtkTextIter *start,
+                                            const 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);
+}
+
+gboolean
+_gtk_source_buffer_has_invalid_chars (GtkSourceBuffer *buffer)
+{
+       GtkTextIter start;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), FALSE);
+
+       if (buffer->priv->invalid_char_tag == NULL)
+       {
+               return FALSE;
+       }
+
+       gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &start);
+
+       if (gtk_text_iter_begins_tag (&start, buffer->priv->invalid_char_tag) ||
+           gtk_text_iter_forward_to_tag_toggle (&start, buffer->priv->invalid_char_tag))
+       {
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+/**
+ * gtk_source_buffer_set_implicit_trailing_newline:
+ * @buffer: a #GtkSourceBuffer.
+ * @implicit_trailing_newline: the new value.
+ *
+ * Sets whether the @buffer has an implicit trailing newline.
+ *
+ * If an explicit trailing newline is present in a #GtkTextBuffer, #GtkTextView
+ * shows it as an empty line. This is generally not what the user expects.
+ *
+ * If @implicit_trailing_newline is %TRUE (the default value):
+ *  - when a #GtkSourceFileLoader loads the content of a file into the @buffer,
+ *    the trailing newline (if present in the file) is not inserted into the
+ *    @buffer.
+ *  - when a #GtkSourceFileSaver saves the content of the @buffer into a file, a
+ *    trailing newline is added to the file.
+ *
+ * On the other hand, if @implicit_trailing_newline is %FALSE, the file's
+ * content is not modified when loaded into the @buffer, and the @buffer's
+ * content is not modified when saved into a file.
+ *
+ * Since: 3.14
+ */
+void
+gtk_source_buffer_set_implicit_trailing_newline (GtkSourceBuffer *buffer,
+                                                gboolean         implicit_trailing_newline)
+{
+       g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
+
+       implicit_trailing_newline = implicit_trailing_newline != FALSE;
+
+       if (buffer->priv->implicit_trailing_newline != implicit_trailing_newline)
+       {
+               buffer->priv->implicit_trailing_newline = implicit_trailing_newline;
+               g_object_notify (G_OBJECT (buffer), "implicit-trailing-newline");
+       }
+}
+
+/**
+ * gtk_source_buffer_get_implicit_trailing_newline:
+ * @buffer: a #GtkSourceBuffer.
+ *
+ * Returns: whether the @buffer has an implicit trailing newline.
+ * Since: 3.14
+ */
+gboolean
+gtk_source_buffer_get_implicit_trailing_newline (GtkSourceBuffer *buffer)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), TRUE);
+
+       return buffer->priv->implicit_trailing_newline;
+}
diff --git a/gtksourceview/gtksourcebuffer.h b/gtksourceview/gtksourcebuffer.h
index f04facb..449cca3 100644
--- a/gtksourceview/gtksourcebuffer.h
+++ b/gtksourceview/gtksourcebuffer.h
@@ -6,6 +6,7 @@
  *                           Chris Phelps <chicane reninet com> and
  *                           Jeroen Zwartepoorte <jeroen xs4all nl>
  * Copyright (C) 2003 - Paolo Maggi, Gustavo Giráldez
+ * 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
@@ -202,6 +203,11 @@ GtkSourceUndoManager       *gtk_source_buffer_get_undo_manager                     
(GtkSourceBuffer        *buf
 void                    gtk_source_buffer_set_undo_manager                     (GtkSourceBuffer        
*buffer,
                                                                                 GtkSourceUndoManager   
*manager);
 
+void                    gtk_source_buffer_set_implicit_trailing_newline        (GtkSourceBuffer        
*buffer,
+                                                                                gboolean                
implicit_trailing_newline);
+
+gboolean                gtk_source_buffer_get_implicit_trailing_newline        (GtkSourceBuffer        
*buffer);
+
 G_END_DECLS
 
 #endif /* __GTK_SOURCE_BUFFER_H__ */
diff --git a/gtksourceview/gtksourcebufferinputstream.c b/gtksourceview/gtksourcebufferinputstream.c
new file mode 100644
index 0000000..7c866db
--- /dev/null
+++ b/gtksourceview/gtksourcebufferinputstream.c
@@ -0,0 +1,515 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* gtksourcebufferinputstream.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 <glib.h>
+#include <gio/gio.h>
+#include <string.h>
+#include "gtksourcebufferinputstream.h"
+#include "gtksourceview-typebuiltins.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.
+ */
+
+struct _GtkSourceBufferInputStreamPrivate
+{
+       GtkTextBuffer *buffer;
+       GtkTextMark *pos;
+       gint bytes_partial;
+
+       GtkSourceNewlineType newline_type;
+
+       guint newline_added : 1;
+       guint is_initialized : 1;
+       guint add_trailing_newline : 1;
+};
+
+enum
+{
+       PROP_0,
+       PROP_BUFFER,
+       PROP_NEWLINE_TYPE,
+       PROP_ADD_TRAILING_NEWLINE
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GtkSourceBufferInputStream, _gtk_source_buffer_input_stream, 
G_TYPE_INPUT_STREAM);
+
+static gsize
+get_new_line_size (GtkSourceBufferInputStream *stream)
+{
+       switch (stream->priv->newline_type)
+       {
+               case GTK_SOURCE_NEWLINE_TYPE_CR:
+               case GTK_SOURCE_NEWLINE_TYPE_LF:
+                       return 1;
+
+               case GTK_SOURCE_NEWLINE_TYPE_CR_LF:
+                       return 2;
+
+               default:
+                       g_warn_if_reached ();
+                       break;
+       }
+
+       return 1;
+}
+
+static const gchar *
+get_new_line (GtkSourceBufferInputStream *stream)
+{
+       switch (stream->priv->newline_type)
+       {
+               case GTK_SOURCE_NEWLINE_TYPE_LF:
+                       return "\n";
+
+               case GTK_SOURCE_NEWLINE_TYPE_CR:
+                       return "\r";
+
+               case GTK_SOURCE_NEWLINE_TYPE_CR_LF:
+                       return "\r\n";
+
+               default:
+                       g_warn_if_reached ();
+                       break;
+       }
+
+       return "\n";
+}
+
+static gsize
+read_line (GtkSourceBufferInputStream *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;
+
+       if (stream->priv->buffer == NULL)
+       {
+               return 0;
+       }
+
+       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 -= 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
+_gtk_source_buffer_input_stream_read (GInputStream  *input_stream,
+                                     void          *buffer,
+                                     gsize          count,
+                                     GCancellable  *cancellable,
+                                     GError       **error)
+{
+       GtkSourceBufferInputStream *stream;
+       GtkTextIter iter;
+       gssize space_left, read, n;
+
+       stream = GTK_SOURCE_BUFFER_INPUT_STREAM (input_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;
+       }
+
+       if (stream->priv->buffer == NULL)
+       {
+               return 0;
+       }
+
+       /* Initialize the mark to the first char in the text buffer */
+       if (!stream->priv->is_initialized)
+       {
+               gtk_text_buffer_get_start_iter (stream->priv->buffer, &iter);
+               stream->priv->pos = gtk_text_buffer_create_mark (stream->priv->buffer,
+                                                                NULL,
+                                                                &iter,
+                                                                FALSE);
+
+               stream->priv->is_initialized = TRUE;
+       }
+
+       space_left = count;
+       read = 0;
+
+       do
+       {
+               n = read_line (stream, (gchar *)buffer + read, space_left);
+               read += n;
+               space_left -= n;
+       } while (space_left > 0 && n != 0 && stream->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 (stream->priv->buffer,
+                                         &iter,
+                                         stream->priv->pos);
+
+       if (gtk_text_iter_is_end (&iter) &&
+           !gtk_text_iter_is_start (&iter) &&
+           stream->priv->add_trailing_newline)
+       {
+               gssize newline_size;
+
+               newline_size = get_new_line_size (stream);
+
+               if (space_left >= newline_size &&
+                   !stream->priv->newline_added)
+               {
+                       const gchar *newline;
+
+                       newline = get_new_line (stream);
+
+                       memcpy ((gchar *)buffer + read, newline, newline_size);
+
+                       read += newline_size;
+                       stream->priv->newline_added = TRUE;
+               }
+       }
+
+       return read;
+}
+
+static gboolean
+_gtk_source_buffer_input_stream_close (GInputStream  *input_stream,
+                                      GCancellable  *cancellable,
+                                      GError       **error)
+{
+       GtkSourceBufferInputStream *stream = GTK_SOURCE_BUFFER_INPUT_STREAM (input_stream);
+
+       stream->priv->newline_added = FALSE;
+
+       if (stream->priv->is_initialized &&
+           stream->priv->buffer != NULL)
+       {
+               gtk_text_buffer_delete_mark (stream->priv->buffer, stream->priv->pos);
+       }
+
+       return TRUE;
+}
+
+static void
+_gtk_source_buffer_input_stream_set_property (GObject      *object,
+                                             guint         prop_id,
+                                             const GValue *value,
+                                             GParamSpec   *pspec)
+{
+       GtkSourceBufferInputStream *stream = GTK_SOURCE_BUFFER_INPUT_STREAM (object);
+
+       switch (prop_id)
+       {
+               case PROP_BUFFER:
+                       g_assert (stream->priv->buffer == NULL);
+                       stream->priv->buffer = g_value_dup_object (value);
+                       break;
+
+               case PROP_NEWLINE_TYPE:
+                       stream->priv->newline_type = g_value_get_enum (value);
+                       break;
+
+               case PROP_ADD_TRAILING_NEWLINE:
+                       stream->priv->add_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_input_stream_get_property (GObject    *object,
+                                         guint       prop_id,
+                                         GValue     *value,
+                                         GParamSpec *pspec)
+{
+       GtkSourceBufferInputStream *stream = GTK_SOURCE_BUFFER_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;
+
+               case PROP_ADD_TRAILING_NEWLINE:
+                       g_value_set_boolean (value, stream->priv->add_trailing_newline);
+                       break;
+
+               default:
+                       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                       break;
+       }
+}
+
+static void
+_gtk_source_buffer_input_stream_dispose (GObject *object)
+{
+       GtkSourceBufferInputStream *stream = GTK_SOURCE_BUFFER_INPUT_STREAM (object);
+
+       g_clear_object (&stream->priv->buffer);
+
+       G_OBJECT_CLASS (_gtk_source_buffer_input_stream_parent_class)->dispose (object);
+}
+
+static void
+_gtk_source_buffer_input_stream_class_init (GtkSourceBufferInputStreamClass *klass)
+{
+       GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+       GInputStreamClass *stream_class = G_INPUT_STREAM_CLASS (klass);
+
+       gobject_class->get_property = _gtk_source_buffer_input_stream_get_property;
+       gobject_class->set_property = _gtk_source_buffer_input_stream_set_property;
+       gobject_class->dispose = _gtk_source_buffer_input_stream_dispose;
+
+       stream_class->read_fn = _gtk_source_buffer_input_stream_read;
+       stream_class->close_fn = _gtk_source_buffer_input_stream_close;
+
+       g_object_class_install_property (gobject_class,
+                                        PROP_BUFFER,
+                                        g_param_spec_object ("buffer",
+                                                             "GtkTextBuffer",
+                                                             "",
+                                                             GTK_TYPE_TEXT_BUFFER,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY |
+                                                             G_PARAM_STATIC_STRINGS));
+
+       /**
+        * GtkSourceBufferInputStream: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",
+                                                           "",
+                                                           GTK_SOURCE_TYPE_NEWLINE_TYPE,
+                                                           GTK_SOURCE_NEWLINE_TYPE_LF,
+                                                           G_PARAM_READWRITE |
+                                                           G_PARAM_STATIC_STRINGS |
+                                                           G_PARAM_CONSTRUCT_ONLY));
+
+       /**
+        * GtkSourceBufferInputStream:add-trailing-newline:
+        *
+        * The :add-trailing-newline property specifies whether or not to
+        * add a trailing newline when reading the buffer.
+        */
+       g_object_class_install_property (gobject_class,
+                                        PROP_ADD_TRAILING_NEWLINE,
+                                        g_param_spec_boolean ("add-trailing-newline",
+                                                              "Add trailing newline",
+                                                              "",
+                                                              TRUE,
+                                                              G_PARAM_READWRITE |
+                                                              G_PARAM_STATIC_STRINGS |
+                                                              G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+_gtk_source_buffer_input_stream_init (GtkSourceBufferInputStream *stream)
+{
+       stream->priv = _gtk_source_buffer_input_stream_get_instance_private (stream);
+}
+
+/**
+ * _gtk_source_buffer_input_stream_new:
+ * @buffer: a #GtkTextBuffer
+ *
+ * Reads the data from @buffer.
+ *
+ * Returns: a new input stream to read @buffer
+ */
+GtkSourceBufferInputStream *
+_gtk_source_buffer_input_stream_new (GtkTextBuffer        *buffer,
+                                    GtkSourceNewlineType  type,
+                                    gboolean              add_trailing_newline)
+{
+       g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
+
+       return g_object_new (GTK_SOURCE_TYPE_BUFFER_INPUT_STREAM,
+                            "buffer", buffer,
+                            "newline-type", type,
+                            "add-trailing-newline", add_trailing_newline,
+                            NULL);
+}
+
+gsize
+_gtk_source_buffer_input_stream_get_total_size (GtkSourceBufferInputStream *stream)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER_INPUT_STREAM (stream), 0);
+
+       if (stream->priv->buffer == NULL)
+       {
+               return 0;
+       }
+
+       return gtk_text_buffer_get_char_count (stream->priv->buffer);
+}
+
+gsize
+_gtk_source_buffer_input_stream_tell (GtkSourceBufferInputStream *stream)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER_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 ||
+           stream->priv->buffer == NULL)
+       {
+               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);
+       }
+}
diff --git a/gtksourceview/gtksourcebufferinputstream.h b/gtksourceview/gtksourcebufferinputstream.h
new file mode 100644
index 0000000..97c460b
--- /dev/null
+++ b/gtksourceview/gtksourcebufferinputstream.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* gtksourcebufferinputstream.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_INPUT_STREAM_H__
+#define __GTK_SOURCE_BUFFER_INPUT_STREAM_H__
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#include "gtksourcetypes-private.h"
+#include "gtksourcebuffer.h"
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_BUFFER_INPUT_STREAM            (_gtk_source_buffer_input_stream_get_type ())
+#define GTK_SOURCE_BUFFER_INPUT_STREAM(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
GTK_SOURCE_TYPE_BUFFER_INPUT_STREAM, GtkSourceBufferInputStream))
+#define GTK_SOURCE_BUFFER_INPUT_STREAM_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), 
GTK_SOURCE_TYPE_BUFFER_INPUT_STREAM, GtkSourceBufferInputStreamClass))
+#define GTK_SOURCE_IS_BUFFER_INPUT_STREAM(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
GTK_SOURCE_TYPE_BUFFER_INPUT_STREAM))
+#define GTK_SOURCE_IS_BUFFER_INPUT_STREAM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), 
GTK_SOURCE_TYPE_BUFFER_INPUT_STREAM))
+#define GTK_SOURCE_BUFFER_INPUT_STREAM_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), 
GTK_SOURCE_TYPE_BUFFER_INPUT_STREAM, GtkSourceBufferInputStreamClass))
+
+typedef struct _GtkSourceBufferInputStreamClass                GtkSourceBufferInputStreamClass;
+typedef struct _GtkSourceBufferInputStreamPrivate      GtkSourceBufferInputStreamPrivate;
+
+struct _GtkSourceBufferInputStream
+{
+       GInputStream parent;
+
+       GtkSourceBufferInputStreamPrivate *priv;
+};
+
+struct _GtkSourceBufferInputStreamClass
+{
+       GInputStreamClass parent_class;
+};
+
+G_GNUC_INTERNAL
+GType           _gtk_source_buffer_input_stream_get_type               (void) G_GNUC_CONST;
+
+G_GNUC_INTERNAL
+GtkSourceBufferInputStream
+               *_gtk_source_buffer_input_stream_new                    (GtkTextBuffer              *buffer,
+                                                                        GtkSourceNewlineType        type,
+                                                                        gboolean                    
add_trailing_newline);
+
+G_GNUC_INTERNAL
+gsize           _gtk_source_buffer_input_stream_get_total_size         (GtkSourceBufferInputStream *stream);
+
+G_GNUC_INTERNAL
+gsize           _gtk_source_buffer_input_stream_tell                   (GtkSourceBufferInputStream *stream);
+
+G_END_DECLS
+
+#endif /* __GTK_SOURCE_BUFFER_INPUT_STREAM_H__ */
diff --git a/gtksourceview/gtksourcebufferoutputstream.c b/gtksourceview/gtksourcebufferoutputstream.c
new file mode 100644
index 0000000..235d7e3
--- /dev/null
+++ b/gtksourceview/gtksourcebufferoutputstream.c
@@ -0,0 +1,1141 @@
+/* -*- 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 "gtksourcefileloader.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
+{
+       GtkSourceBuffer *source_buffer;
+       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 remove_trailing_newline : 1;
+};
+
+enum
+{
+       PROP_0,
+       PROP_BUFFER,
+       PROP_REMOVE_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:
+                       g_assert (stream->priv->source_buffer == NULL);
+                       stream->priv->source_buffer = g_value_dup_object (value);
+                       break;
+
+               case PROP_REMOVE_TRAILING_NEWLINE:
+                       stream->priv->remove_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->source_buffer);
+                       break;
+
+               case PROP_REMOVE_TRAILING_NEWLINE:
+                       g_value_set_boolean (value, stream->priv->remove_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->source_buffer);
+       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->source_buffer == 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->source_buffer);
+
+       gtk_text_buffer_set_text (GTK_TEXT_BUFFER (stream->priv->source_buffer), "", 0);
+       gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (stream->priv->source_buffer), FALSE);
+
+       gtk_source_buffer_end_not_undoable_action (stream->priv->source_buffer);
+
+       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",
+                                                             "GtkSourceBuffer",
+                                                             "",
+                                                             GTK_SOURCE_TYPE_BUFFER,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY |
+                                                             G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (object_class,
+                                        PROP_REMOVE_TRAILING_NEWLINE,
+                                        g_param_spec_boolean ("remove-trailing-newline",
+                                                              "Remove 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;
+}
+
+GtkSourceBufferOutputStream *
+gtk_source_buffer_output_stream_new (GtkSourceBuffer *buffer,
+                                    GSList          *candidate_encodings,
+                                    gboolean         remove_trailing_newline)
+{
+       GtkSourceBufferOutputStream *stream;
+
+       stream = g_object_new (GTK_SOURCE_TYPE_BUFFER_OUTPUT_STREAM,
+                              "buffer", buffer,
+                              "remove-trailing-newline", remove_trailing_newline,
+                              NULL);
+
+       stream->priv->encodings = g_slist_copy (candidate_encodings);
+
+       return 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);
+
+       if (stream->priv->source_buffer == NULL)
+       {
+               return GTK_SOURCE_NEWLINE_TYPE_DEFAULT;
+       }
+
+       type = GTK_SOURCE_NEWLINE_TYPE_DEFAULT;
+
+       gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (stream->priv->source_buffer),
+                                       &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 ||
+           stream->priv->source_buffer == NULL)
+       {
+               return;
+       }
+
+       gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (stream->priv->source_buffer),
+                                           &start, stream->priv->error_offset);
+
+       _gtk_source_buffer_set_as_invalid_character (stream->priv->source_buffer,
+                                                    &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 (stream->priv->source_buffer == NULL)
+       {
+               return;
+       }
+
+       /* 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->source_buffer),
+                               &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;
+
+       if (stream->priv->source_buffer == NULL)
+       {
+               return;
+       }
+
+       text_buffer = GTK_TEXT_BUFFER (stream->priv->source_buffer);
+       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;
+       }
+}
+
+static void
+remove_trailing_newline (GtkSourceBufferOutputStream *stream)
+{
+       GtkTextIter end;
+       GtkTextIter start;
+
+       if (stream->priv->source_buffer == NULL)
+       {
+               return;
+       }
+
+       gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (stream->priv->source_buffer), &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);
+               }
+
+               gtk_text_buffer_delete (GTK_TEXT_BUFFER (stream->priv->source_buffer),
+                                       &start,
+                                       &end);
+       }
+}
+
+static void
+end_append_text_to_document (GtkSourceBufferOutputStream *stream)
+{
+       if (stream->priv->source_buffer == NULL)
+       {
+               return;
+       }
+
+       if (stream->priv->remove_trailing_newline)
+       {
+               remove_trailing_newline (stream);
+       }
+
+       gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (stream->priv->source_buffer),
+                                     FALSE);
+
+       gtk_source_buffer_end_not_undoable_action (stream->priv->source_buffer);
+}
+
+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:
+                                       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;
+
+       ostream = GTK_SOURCE_BUFFER_OUTPUT_STREAM (stream);
+
+       if (g_cancellable_set_error_if_cancelled (cancellable, error) ||
+           ostream->priv->source_buffer == NULL)
+       {
+               return -1;
+       }
+
+       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_LOADER_ERROR,
+                                            GTK_SOURCE_FILE_LOADER_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 (ostream->priv->source_buffer);
+
+               gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (ostream->priv->source_buffer),
+                                               &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 ||
+           ostream->priv->source_buffer == NULL)
+       {
+               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->source_buffer),
+                                       &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..041c25e
--- /dev/null
+++ b/gtksourceview/gtksourcebufferoutputstream.h
@@ -0,0 +1,77 @@
+/* -*- 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 "gtksourcebuffer.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
+GtkSourceBufferOutputStream
+                       *gtk_source_buffer_output_stream_new            (GtkSourceBuffer             *buffer,
+                                                                        GSList                      
*candidate_encodings,
+                                                                        gboolean                     
remove_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/gtksourceencoding.c b/gtksourceview/gtksourceencoding.c
new file mode 100644
index 0000000..c84c81a
--- /dev/null
+++ b/gtksourceview/gtksourceencoding.c
@@ -0,0 +1,589 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* gtksourceencoding.c
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2002-2005 - Paolo Maggi
+ * 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 "gtksourceencoding.h"
+#include "gtksourceview-i18n.h"
+
+/**
+ * SECTION:encoding
+ * @Short_description: Character encoding
+ * @Title: GtkSourceEncoding
+ * @See_also: #GtkSourceFileSaver, #GtkSourceFileLoader
+ *
+ * The #GtkSourceEncoding boxed type represents a character encoding. It is used
+ * for example by the #GtkSourceFileSaver and #GtkSourceFileLoader. Note that
+ * the text in GTK+ widgets is always encoded in UTF-8.
+ */
+
+struct _GtkSourceEncoding
+{
+       gint index;
+       const gchar *charset;
+       const gchar *name;
+};
+
+G_DEFINE_BOXED_TYPE (GtkSourceEncoding, gtk_source_encoding,
+                     gtk_source_encoding_copy,
+                     gtk_source_encoding_free)
+
+/*
+ * The original versions of the following tables are taken from profterm
+ *
+ * Copyright (C) 2002 Red Hat, Inc.
+ */
+
+typedef enum
+{
+       GTK_SOURCE_ENCODING_ISO_8859_1,
+       GTK_SOURCE_ENCODING_ISO_8859_2,
+       GTK_SOURCE_ENCODING_ISO_8859_3,
+       GTK_SOURCE_ENCODING_ISO_8859_4,
+       GTK_SOURCE_ENCODING_ISO_8859_5,
+       GTK_SOURCE_ENCODING_ISO_8859_6,
+       GTK_SOURCE_ENCODING_ISO_8859_7,
+       GTK_SOURCE_ENCODING_ISO_8859_8,
+       GTK_SOURCE_ENCODING_ISO_8859_9,
+       GTK_SOURCE_ENCODING_ISO_8859_10,
+       GTK_SOURCE_ENCODING_ISO_8859_13,
+       GTK_SOURCE_ENCODING_ISO_8859_14,
+       GTK_SOURCE_ENCODING_ISO_8859_15,
+       GTK_SOURCE_ENCODING_ISO_8859_16,
+
+       GTK_SOURCE_ENCODING_UTF_7,
+       GTK_SOURCE_ENCODING_UTF_16,
+       GTK_SOURCE_ENCODING_UTF_16_BE,
+       GTK_SOURCE_ENCODING_UTF_16_LE,
+       GTK_SOURCE_ENCODING_UTF_32,
+       GTK_SOURCE_ENCODING_UCS_2,
+       GTK_SOURCE_ENCODING_UCS_4,
+
+       GTK_SOURCE_ENCODING_ARMSCII_8,
+       GTK_SOURCE_ENCODING_BIG5,
+       GTK_SOURCE_ENCODING_BIG5_HKSCS,
+       GTK_SOURCE_ENCODING_CP_866,
+
+       GTK_SOURCE_ENCODING_EUC_JP,
+       GTK_SOURCE_ENCODING_EUC_JP_MS,
+       GTK_SOURCE_ENCODING_CP932,
+       GTK_SOURCE_ENCODING_EUC_KR,
+       GTK_SOURCE_ENCODING_EUC_TW,
+
+       GTK_SOURCE_ENCODING_GB18030,
+       GTK_SOURCE_ENCODING_GB2312,
+       GTK_SOURCE_ENCODING_GBK,
+       GTK_SOURCE_ENCODING_GEOSTD8,
+
+       GTK_SOURCE_ENCODING_IBM_850,
+       GTK_SOURCE_ENCODING_IBM_852,
+       GTK_SOURCE_ENCODING_IBM_855,
+       GTK_SOURCE_ENCODING_IBM_857,
+       GTK_SOURCE_ENCODING_IBM_862,
+       GTK_SOURCE_ENCODING_IBM_864,
+
+       GTK_SOURCE_ENCODING_ISO_2022_JP,
+       GTK_SOURCE_ENCODING_ISO_2022_KR,
+       GTK_SOURCE_ENCODING_ISO_IR_111,
+       GTK_SOURCE_ENCODING_JOHAB,
+       GTK_SOURCE_ENCODING_KOI8_R,
+       GTK_SOURCE_ENCODING_KOI8__R,
+       GTK_SOURCE_ENCODING_KOI8_U,
+
+       GTK_SOURCE_ENCODING_SHIFT_JIS,
+       GTK_SOURCE_ENCODING_TCVN,
+       GTK_SOURCE_ENCODING_TIS_620,
+       GTK_SOURCE_ENCODING_UHC,
+       GTK_SOURCE_ENCODING_VISCII,
+
+       GTK_SOURCE_ENCODING_WINDOWS_1250,
+       GTK_SOURCE_ENCODING_WINDOWS_1251,
+       GTK_SOURCE_ENCODING_WINDOWS_1252,
+       GTK_SOURCE_ENCODING_WINDOWS_1253,
+       GTK_SOURCE_ENCODING_WINDOWS_1254,
+       GTK_SOURCE_ENCODING_WINDOWS_1255,
+       GTK_SOURCE_ENCODING_WINDOWS_1256,
+       GTK_SOURCE_ENCODING_WINDOWS_1257,
+       GTK_SOURCE_ENCODING_WINDOWS_1258,
+
+       GTK_SOURCE_ENCODING_LAST,
+
+       GTK_SOURCE_ENCODING_UTF_8,
+       GTK_SOURCE_ENCODING_UNKNOWN
+} GtkSourceEncodingIndex;
+
+static const GtkSourceEncoding utf8_encoding =
+{
+       GTK_SOURCE_ENCODING_UTF_8,
+       "UTF-8",
+       N_("Unicode")
+};
+
+/* Initialized in gtk_source_encoding_lazy_init(). */
+static GtkSourceEncoding unknown_encoding =
+{
+       GTK_SOURCE_ENCODING_UNKNOWN,
+       NULL,
+       NULL
+};
+
+static const GtkSourceEncoding encodings[] =
+{
+       { GTK_SOURCE_ENCODING_ISO_8859_1,
+         "ISO-8859-1", N_("Western") },
+       { GTK_SOURCE_ENCODING_ISO_8859_2,
+        "ISO-8859-2", N_("Central European") },
+       { GTK_SOURCE_ENCODING_ISO_8859_3,
+         "ISO-8859-3", N_("South European") },
+       { GTK_SOURCE_ENCODING_ISO_8859_4,
+         "ISO-8859-4", N_("Baltic") },
+       { GTK_SOURCE_ENCODING_ISO_8859_5,
+         "ISO-8859-5", N_("Cyrillic") },
+       { GTK_SOURCE_ENCODING_ISO_8859_6,
+         "ISO-8859-6", N_("Arabic") },
+       { GTK_SOURCE_ENCODING_ISO_8859_7,
+         "ISO-8859-7", N_("Greek") },
+       { GTK_SOURCE_ENCODING_ISO_8859_8,
+         "ISO-8859-8", N_("Hebrew Visual") },
+       { GTK_SOURCE_ENCODING_ISO_8859_9,
+         "ISO-8859-9", N_("Turkish") },
+       { GTK_SOURCE_ENCODING_ISO_8859_10,
+         "ISO-8859-10", N_("Nordic") },
+       { GTK_SOURCE_ENCODING_ISO_8859_13,
+         "ISO-8859-13", N_("Baltic") },
+       { GTK_SOURCE_ENCODING_ISO_8859_14,
+         "ISO-8859-14", N_("Celtic") },
+       { GTK_SOURCE_ENCODING_ISO_8859_15,
+         "ISO-8859-15", N_("Western") },
+       { GTK_SOURCE_ENCODING_ISO_8859_16,
+         "ISO-8859-16", N_("Romanian") },
+
+       { GTK_SOURCE_ENCODING_UTF_7,
+         "UTF-7", N_("Unicode") },
+       { GTK_SOURCE_ENCODING_UTF_16,
+         "UTF-16", N_("Unicode") },
+       { GTK_SOURCE_ENCODING_UTF_16_BE,
+         "UTF-16BE", N_("Unicode") },
+       { GTK_SOURCE_ENCODING_UTF_16_LE,
+         "UTF-16LE", N_("Unicode") },
+       { GTK_SOURCE_ENCODING_UTF_32,
+         "UTF-32", N_("Unicode") },
+       { GTK_SOURCE_ENCODING_UCS_2,
+         "UCS-2", N_("Unicode") },
+       { GTK_SOURCE_ENCODING_UCS_4,
+         "UCS-4", N_("Unicode") },
+
+       { GTK_SOURCE_ENCODING_ARMSCII_8,
+         "ARMSCII-8", N_("Armenian") },
+       { GTK_SOURCE_ENCODING_BIG5,
+         "BIG5", N_("Chinese Traditional") },
+       { GTK_SOURCE_ENCODING_BIG5_HKSCS,
+         "BIG5-HKSCS", N_("Chinese Traditional") },
+       { GTK_SOURCE_ENCODING_CP_866,
+         "CP866", N_("Cyrillic/Russian") },
+
+       { GTK_SOURCE_ENCODING_EUC_JP,
+         "EUC-JP", N_("Japanese") },
+       { GTK_SOURCE_ENCODING_EUC_JP_MS,
+         "EUC-JP-MS", N_("Japanese") },
+       { GTK_SOURCE_ENCODING_CP932,
+         "CP932", N_("Japanese") },
+
+       { GTK_SOURCE_ENCODING_EUC_KR,
+         "EUC-KR", N_("Korean") },
+       { GTK_SOURCE_ENCODING_EUC_TW,
+         "EUC-TW", N_("Chinese Traditional") },
+
+       { GTK_SOURCE_ENCODING_GB18030,
+         "GB18030", N_("Chinese Simplified") },
+       { GTK_SOURCE_ENCODING_GB2312,
+         "GB2312", N_("Chinese Simplified") },
+       { GTK_SOURCE_ENCODING_GBK,
+         "GBK", N_("Chinese Simplified") },
+       { GTK_SOURCE_ENCODING_GEOSTD8,
+         "GEORGIAN-ACADEMY", N_("Georgian") }, /* FIXME GEOSTD8 ? */
+
+       { GTK_SOURCE_ENCODING_IBM_850,
+         "IBM850", N_("Western") },
+       { GTK_SOURCE_ENCODING_IBM_852,
+         "IBM852", N_("Central European") },
+       { GTK_SOURCE_ENCODING_IBM_855,
+         "IBM855", N_("Cyrillic") },
+       { GTK_SOURCE_ENCODING_IBM_857,
+         "IBM857", N_("Turkish") },
+       { GTK_SOURCE_ENCODING_IBM_862,
+         "IBM862", N_("Hebrew") },
+       { GTK_SOURCE_ENCODING_IBM_864,
+         "IBM864", N_("Arabic") },
+
+       { GTK_SOURCE_ENCODING_ISO_2022_JP,
+         "ISO-2022-JP", N_("Japanese") },
+       { GTK_SOURCE_ENCODING_ISO_2022_KR,
+         "ISO-2022-KR", N_("Korean") },
+       { GTK_SOURCE_ENCODING_ISO_IR_111,
+         "ISO-IR-111", N_("Cyrillic") },
+       { GTK_SOURCE_ENCODING_JOHAB,
+         "JOHAB", N_("Korean") },
+       { GTK_SOURCE_ENCODING_KOI8_R,
+         "KOI8R", N_("Cyrillic") },
+       { GTK_SOURCE_ENCODING_KOI8__R,
+         "KOI8-R", N_("Cyrillic") },
+       { GTK_SOURCE_ENCODING_KOI8_U,
+         "KOI8U", N_("Cyrillic/Ukrainian") },
+
+       { GTK_SOURCE_ENCODING_SHIFT_JIS,
+         "SHIFT_JIS", N_("Japanese") },
+       { GTK_SOURCE_ENCODING_TCVN,
+         "TCVN", N_("Vietnamese") },
+       { GTK_SOURCE_ENCODING_TIS_620,
+         "TIS-620", N_("Thai") },
+       { GTK_SOURCE_ENCODING_UHC,
+         "UHC", N_("Korean") },
+       { GTK_SOURCE_ENCODING_VISCII,
+         "VISCII", N_("Vietnamese") },
+
+       { GTK_SOURCE_ENCODING_WINDOWS_1250,
+         "WINDOWS-1250", N_("Central European") },
+       { GTK_SOURCE_ENCODING_WINDOWS_1251,
+         "WINDOWS-1251", N_("Cyrillic") },
+       { GTK_SOURCE_ENCODING_WINDOWS_1252,
+         "WINDOWS-1252", N_("Western") },
+       { GTK_SOURCE_ENCODING_WINDOWS_1253,
+         "WINDOWS-1253", N_("Greek") },
+       { GTK_SOURCE_ENCODING_WINDOWS_1254,
+         "WINDOWS-1254", N_("Turkish") },
+       { GTK_SOURCE_ENCODING_WINDOWS_1255,
+         "WINDOWS-1255", N_("Hebrew") },
+       { GTK_SOURCE_ENCODING_WINDOWS_1256,
+         "WINDOWS-1256", N_("Arabic") },
+       { GTK_SOURCE_ENCODING_WINDOWS_1257,
+         "WINDOWS-1257", N_("Baltic") },
+       { GTK_SOURCE_ENCODING_WINDOWS_1258,
+         "WINDOWS-1258", N_("Vietnamese") }
+};
+
+static void
+gtk_source_encoding_lazy_init (void)
+{
+       static gboolean initialized = FALSE;
+       const gchar *locale_charset;
+
+       if (G_LIKELY (initialized))
+       {
+               return;
+       }
+
+       if (g_get_charset (&locale_charset) == FALSE)
+       {
+               unknown_encoding.charset = g_strdup (locale_charset);
+       }
+
+       initialized = TRUE;
+}
+
+/**
+ * gtk_source_encoding_get_from_charset:
+ * @charset: a character set.
+ *
+ * Gets a #GtkSourceEncoding from a character set such as "UTF-8" or
+ * "ISO-8859-1".
+ *
+ * Returns: the corresponding #GtkSourceEncoding, or %NULL if not found.
+ * Since: 3.14
+ */
+const GtkSourceEncoding *
+gtk_source_encoding_get_from_charset (const gchar *charset)
+{
+       gint i;
+
+       g_return_val_if_fail (charset != NULL, NULL);
+
+       if (g_ascii_strcasecmp (charset, "UTF-8") == 0)
+       {
+               return gtk_source_encoding_get_utf8 ();
+       }
+
+       for (i = 0; i < GTK_SOURCE_ENCODING_LAST; i++)
+       {
+               if (g_ascii_strcasecmp (charset, encodings[i].charset) == 0)
+               {
+                       return &encodings[i];
+               }
+       }
+
+       gtk_source_encoding_lazy_init ();
+
+       if (unknown_encoding.charset != NULL &&
+           g_ascii_strcasecmp (charset, unknown_encoding.charset) == 0)
+       {
+               return &unknown_encoding;
+       }
+
+       return NULL;
+}
+
+/**
+ * gtk_source_encoding_get_from_index:
+ * @index: the index.
+ *
+ * Gets the #GtkSourceEncoding located at @index. The first valid index is 0.
+ * This function can be used in a loop that stops when %NULL is returned.
+ *
+ * Returns: the #GtkSourceEncoding at @index, or %NULL.
+ * Since: 3.14
+ */
+const GtkSourceEncoding *
+gtk_source_encoding_get_from_index (gint index)
+{
+       g_return_val_if_fail (index >= 0, NULL);
+
+       if (index >= GTK_SOURCE_ENCODING_LAST)
+       {
+               return NULL;
+       }
+
+       return &encodings[index];
+}
+
+/**
+ * gtk_source_encoding_get_utf8:
+ *
+ * Returns: the UTF-8 encoding.
+ * Since: 3.14
+ */
+const GtkSourceEncoding *
+gtk_source_encoding_get_utf8 (void)
+{
+       return &utf8_encoding;
+}
+
+/**
+ * gtk_source_encoding_get_current:
+ *
+ * Gets the #GtkSourceEncoding for the current locale. See also g_get_charset().
+ *
+ * Returns: the current locale encoding.
+ * Since: 3.14
+ */
+const GtkSourceEncoding *
+gtk_source_encoding_get_current (void)
+{
+       static gboolean initialized = FALSE;
+       static const GtkSourceEncoding *locale_encoding = NULL;
+
+       const gchar *locale_charset;
+
+       gtk_source_encoding_lazy_init ();
+
+       if (G_LIKELY (initialized))
+       {
+               return locale_encoding;
+       }
+
+       if (g_get_charset (&locale_charset))
+       {
+               locale_encoding = &utf8_encoding;
+       }
+       else
+       {
+               locale_encoding = gtk_source_encoding_get_from_charset (locale_charset);
+       }
+
+       if (locale_encoding == NULL)
+       {
+               locale_encoding = &unknown_encoding;
+       }
+
+       initialized = TRUE;
+
+       return locale_encoding;
+}
+
+/**
+ * gtk_source_encoding_to_string:
+ * @enc: a #GtkSourceEncoding.
+ *
+ * Returns: a string representation. Free with g_free() when no longer needed.
+ * Since: 3.14
+ */
+gchar *
+gtk_source_encoding_to_string (const GtkSourceEncoding* enc)
+{
+       g_return_val_if_fail (enc != NULL, NULL);
+
+       gtk_source_encoding_lazy_init ();
+
+       g_return_val_if_fail (enc->charset != NULL, NULL);
+
+       if (enc->name != NULL)
+       {
+               return g_strdup_printf ("%s (%s)", _(enc->name), enc->charset);
+       }
+       else if (g_ascii_strcasecmp (enc->charset, "ANSI_X3.4-1968") == 0)
+       {
+               return g_strdup_printf ("US-ASCII (%s)", enc->charset);
+       }
+       else
+       {
+               return g_strdup (enc->charset);
+       }
+}
+
+/**
+ * gtk_source_encoding_get_charset:
+ * @enc: a #GtkSourceEncoding.
+ *
+ * Gets the character set of the #GtkSourceEncoding, such as "UTF-8" or
+ * "ISO-8859-1".
+ *
+ * Returns: the character set of the #GtkSourceEncoding.
+ * Since: 3.14
+ */
+const gchar *
+gtk_source_encoding_get_charset (const GtkSourceEncoding* enc)
+{
+       g_return_val_if_fail (enc != NULL, NULL);
+
+       gtk_source_encoding_lazy_init ();
+
+       g_return_val_if_fail (enc->charset != NULL, NULL);
+
+       return enc->charset;
+}
+
+/**
+ * gtk_source_encoding_get_name:
+ * @enc: a #GtkSourceEncoding.
+ *
+ * Gets the name of the #GtkSourceEncoding such as "Unicode" or "Western".
+ *
+ * Returns: the name of the #GtkSourceEncoding.
+ * Since: 3.14
+ */
+const gchar *
+gtk_source_encoding_get_name (const GtkSourceEncoding* enc)
+{
+       g_return_val_if_fail (enc != NULL, NULL);
+
+       gtk_source_encoding_lazy_init ();
+
+       return (enc->name == NULL) ? _("Unknown") : _(enc->name);
+}
+
+/**
+ * gtk_source_encoding_copy:
+ * @enc: a #GtkSourceEncoding.
+ *
+ * Used by language bindings.
+ *
+ * Returns: a copy of @enc.
+ * Since: 3.14
+ */
+GtkSourceEncoding *
+gtk_source_encoding_copy (const GtkSourceEncoding *enc)
+{
+       g_return_val_if_fail (enc != NULL, NULL);
+
+       return (GtkSourceEncoding *) enc;
+}
+
+/**
+ * gtk_source_encoding_free:
+ * @enc: a #GtkSourceEncoding.
+ *
+ * Used by language bindings.
+ *
+ * Since: 3.14
+ */
+void
+gtk_source_encoding_free (GtkSourceEncoding *enc)
+{
+       g_return_if_fail (enc != NULL);
+}
+
+#if 0
+/* Will probably be used in the future. */
+static gboolean
+data_exists (GSList         *list,
+            const gpointer  data)
+{
+       for (; list != NULL; list = g_slist_next (list))
+       {
+               if (list->data == data)
+               {
+                       return TRUE;
+               }
+       }
+
+       return FALSE;
+}
+
+GSList *
+_gtk_source_encoding_strv_to_list (const gchar * const *enc_str)
+{
+       GSList *res = NULL;
+       gchar **p;
+
+       for (p = (gchar **)enc_str; p != NULL && *p != NULL; p++)
+       {
+               const gchar *charset = *p;
+               const GtkSourceEncoding *enc;
+
+               if (g_str_equal (charset, "CURRENT"))
+               {
+                       g_get_charset (&charset);
+               }
+
+               g_return_val_if_fail (charset != NULL, NULL);
+               enc = gtk_source_encoding_get_from_charset (charset);
+
+               if (enc != NULL &&
+                   !data_exists (res, (gpointer)enc))
+               {
+                       res = g_slist_prepend (res, (gpointer)enc);
+               }
+       }
+
+       return g_slist_reverse (res);
+}
+
+gchar **
+_gtk_source_encoding_list_to_strv (const GSList *enc_list)
+{
+       GSList *l;
+       GPtrArray *array;
+
+       array = g_ptr_array_sized_new (g_slist_length ((GSList *)enc_list) + 1);
+
+       for (l = (GSList *)enc_list; l != NULL; l = g_slist_next (l))
+       {
+               const GtkSourceEncoding *enc = l->data;
+               const gchar *charset = gtk_source_encoding_get_charset (enc);
+
+               g_return_val_if_fail (charset != NULL, NULL);
+
+               g_ptr_array_add (array, g_strdup (charset));
+       }
+
+       g_ptr_array_add (array, NULL);
+
+       return (gchar **)g_ptr_array_free (array, FALSE);
+}
+#endif
diff --git a/gtksourceview/gtksourceencoding.h b/gtksourceview/gtksourceencoding.h
new file mode 100644
index 0000000..545806b
--- /dev/null
+++ b/gtksourceview/gtksourceencoding.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* gtksourceencoding.h
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2002-2005 - Paolo Maggi
+ * 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_ENCODING_H__
+#define __GTK_SOURCE_ENCODING_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtksourceview/gtksourcetypes.h>
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_ENCODING (gtk_source_encoding_get_type ())
+
+GType                   gtk_source_encoding_get_type           (void) G_GNUC_CONST;
+
+const GtkSourceEncoding        *gtk_source_encoding_get_from_charset   (const gchar             *charset);
+const GtkSourceEncoding        *gtk_source_encoding_get_from_index     (gint                     index);
+
+gchar                  *gtk_source_encoding_to_string          (const GtkSourceEncoding *enc);
+
+const gchar            *gtk_source_encoding_get_name           (const GtkSourceEncoding *enc);
+const gchar            *gtk_source_encoding_get_charset        (const GtkSourceEncoding *enc);
+
+const GtkSourceEncoding        *gtk_source_encoding_get_utf8           (void);
+const GtkSourceEncoding        *gtk_source_encoding_get_current        (void);
+
+/* These should not be used, they are just to make python bindings happy */
+GtkSourceEncoding      *gtk_source_encoding_copy               (const GtkSourceEncoding *enc);
+void                    gtk_source_encoding_free               (GtkSourceEncoding       *enc);
+
+#if 0
+/* Will probably be used in the future. */
+G_GNUC_INTERNAL
+GSList                 *_gtk_source_encoding_strv_to_list      (const gchar * const     *enc_str);
+
+G_GNUC_INTERNAL
+gchar                 **_gtk_source_encoding_list_to_strv      (const GSList            *enc);
+#endif
+
+G_END_DECLS
+
+#endif  /* __GTK_SOURCE_ENCODING_H__ */
diff --git a/gtksourceview/gtksourcefile.c b/gtksourceview/gtksourcefile.c
new file mode 100644
index 0000000..08195ba
--- /dev/null
+++ b/gtksourceview/gtksourcefile.c
@@ -0,0 +1,464 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* gtksourcefile.c
+ * This file is part of GtkSourceView
+ *
+ * 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 "gtksourcefile.h"
+#include "gtksourceencoding.h"
+#include "gtksourceview-typebuiltins.h"
+#include "gtksourceview-i18n.h"
+
+/**
+ * SECTION:file
+ * @Short_description: On-disk representation of a GtkSourceBuffer
+ * @Title: GtkSourceFile
+ * @See_also: #GtkSourceFileLoader, #GtkSourceFileSaver
+ *
+ * A #GtkSourceFile object is the on-disk representation of a #GtkSourceBuffer.
+ * With a #GtkSourceFile, you can create and configure a #GtkSourceFileLoader
+ * and #GtkSourceFileSaver which take by default the values of the
+ * #GtkSourceFile properties (except for the file loader which auto-detect some
+ * properties). On a successful load or save operation, the #GtkSourceFile
+ * properties are updated. If an operation fails, the #GtkSourceFile properties
+ * have still the previous valid values.
+ */
+
+enum
+{
+       PROP_0,
+       PROP_LOCATION,
+       PROP_ENCODING,
+       PROP_NEWLINE_TYPE,
+       PROP_COMPRESSION_TYPE
+};
+
+struct _GtkSourceFilePrivate
+{
+       GFile *location;
+       const GtkSourceEncoding *encoding;
+       GtkSourceNewlineType newline_type;
+       GtkSourceCompressionType compression_type;
+
+       GtkSourceMountOperationFactory mount_operation_factory;
+       gpointer mount_operation_userdata;
+       GDestroyNotify mount_operation_notify;
+
+       /* Last known modification time of 'location'. The value is updated on a
+        * file loading or file saving.
+        */
+       GTimeVal modification_time;
+
+       guint modification_time_set : 1;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GtkSourceFile, gtk_source_file, G_TYPE_OBJECT)
+
+static void
+gtk_source_file_get_property (GObject    *object,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+       GtkSourceFile *file;
+
+       g_return_if_fail (GTK_SOURCE_IS_FILE (object));
+
+       file = GTK_SOURCE_FILE (object);
+
+       switch (prop_id)
+       {
+               case PROP_LOCATION:
+                       g_value_set_object (value, file->priv->location);
+                       break;
+
+               case PROP_ENCODING:
+                       g_value_set_boxed (value, file->priv->encoding);
+                       break;
+
+               case PROP_NEWLINE_TYPE:
+                       g_value_set_enum (value, file->priv->newline_type);
+                       break;
+
+               case PROP_COMPRESSION_TYPE:
+                       g_value_set_enum (value, file->priv->compression_type);
+                       break;
+
+               default:
+                       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                       break;
+       }
+}
+
+static void
+gtk_source_file_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+       GtkSourceFile *file;
+
+       g_return_if_fail (GTK_SOURCE_IS_FILE (object));
+
+       file = GTK_SOURCE_FILE (object);
+
+       switch (prop_id)
+       {
+               case PROP_LOCATION:
+                       gtk_source_file_set_location (file, g_value_get_object (value));
+                       break;
+
+               default:
+                       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                       break;
+       }
+}
+
+static void
+gtk_source_file_dispose (GObject *object)
+{
+       GtkSourceFile *file = GTK_SOURCE_FILE (object);
+
+       g_clear_object (&file->priv->location);
+
+       if (file->priv->mount_operation_notify != NULL)
+       {
+               file->priv->mount_operation_notify (file->priv->mount_operation_userdata);
+               file->priv->mount_operation_notify = NULL;
+       }
+
+       G_OBJECT_CLASS (gtk_source_file_parent_class)->dispose (object);
+}
+
+static void
+gtk_source_file_class_init (GtkSourceFileClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->get_property = gtk_source_file_get_property;
+       object_class->set_property = gtk_source_file_set_property;
+       object_class->dispose = gtk_source_file_dispose;
+
+       /**
+        * GtkSourceFile:location:
+        *
+        * The location.
+        *
+        * Since: 3.14
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_LOCATION,
+                                        g_param_spec_object ("location",
+                                                             _("Location"),
+                                                             "",
+                                                             G_TYPE_FILE,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_STATIC_STRINGS));
+
+       /**
+        * GtkSourceFile:encoding:
+        *
+        * The character encoding.
+        *
+        * Since: 3.14
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_ENCODING,
+                                        g_param_spec_boxed ("encoding",
+                                                            _("Encoding"),
+                                                            "",
+                                                            GTK_SOURCE_TYPE_ENCODING,
+                                                            G_PARAM_READABLE |
+                                                            G_PARAM_STATIC_STRINGS));
+
+       /**
+        * GtkSourceFile:newline-type:
+        *
+        * The line ending type.
+        *
+        * Since: 3.14
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_NEWLINE_TYPE,
+                                        g_param_spec_enum ("newline-type",
+                                                           _("Newline type"),
+                                                           "",
+                                                           GTK_SOURCE_TYPE_NEWLINE_TYPE,
+                                                           GTK_SOURCE_NEWLINE_TYPE_LF,
+                                                           G_PARAM_READABLE |
+                                                           G_PARAM_STATIC_STRINGS));
+
+       /**
+        * GtkSourceFile:compression-type:
+        *
+        * The compression type.
+        *
+        * Since: 3.14
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_COMPRESSION_TYPE,
+                                        g_param_spec_enum ("compression-type",
+                                                           _("Compression type"),
+                                                           "",
+                                                           GTK_SOURCE_TYPE_COMPRESSION_TYPE,
+                                                           GTK_SOURCE_COMPRESSION_TYPE_NONE,
+                                                           G_PARAM_READABLE |
+                                                           G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gtk_source_file_init (GtkSourceFile *file)
+{
+       file->priv = gtk_source_file_get_instance_private (file);
+
+       file->priv->encoding = gtk_source_encoding_get_utf8 ();
+}
+
+/**
+ * gtk_source_file_new:
+ *
+ * Returns: a new #GtkSourceFile object.
+ * Since: 3.14
+ */
+GtkSourceFile *
+gtk_source_file_new (void)
+{
+       return g_object_new (GTK_SOURCE_TYPE_FILE, NULL);
+}
+
+/**
+ * gtk_source_file_set_location:
+ * @file: a #GtkSourceFile.
+ * @location: (nullable): the new #GFile, or %NULL.
+ *
+ * Sets the location. Most of the time it is better to change the location with a
+ * #GtkSourceFileLoader or #GtkSourceFileSaver. By doing so, the location of
+ * #GtkSourceFile is updated only when the file loading or saving have
+ * succeeded.
+ *
+ * One use case of this function is when your application renames the file (with
+ * g_file_move() for instance). In this case you don't want to reload the file
+ * from the new location.
+ *
+ * Since: 3.14
+ */
+void
+gtk_source_file_set_location (GtkSourceFile *file,
+                             GFile         *location)
+{
+       g_return_if_fail (GTK_SOURCE_IS_FILE (file));
+       g_return_if_fail (location == NULL || G_IS_FILE (location));
+
+       if (file->priv->location != location)
+       {
+               g_clear_object (&file->priv->location);
+               file->priv->location = location;
+
+               if (location != NULL)
+               {
+                       g_object_ref (location);
+               }
+
+               g_object_notify (G_OBJECT (file), "location");
+
+               /* The modification_time is for the old location. */
+               file->priv->modification_time_set = FALSE;
+       }
+}
+
+/**
+ * gtk_source_file_get_location:
+ * @file: a #GtkSourceFile.
+ *
+ * Returns: (transfer none): the #GFile.
+ * Since: 3.14
+ */
+GFile *
+gtk_source_file_get_location (GtkSourceFile *file)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), NULL);
+
+       return file->priv->location;
+}
+
+void
+_gtk_source_file_set_encoding (GtkSourceFile           *file,
+                              const GtkSourceEncoding *encoding)
+{
+       g_return_if_fail (GTK_SOURCE_IS_FILE (file));
+
+       if (encoding == NULL)
+       {
+               encoding = gtk_source_encoding_get_utf8 ();
+       }
+
+       if (file->priv->encoding != encoding)
+       {
+               file->priv->encoding = encoding;
+               g_object_notify (G_OBJECT (file), "encoding");
+       }
+}
+
+/**
+ * gtk_source_file_get_encoding:
+ * @file: a #GtkSourceFile.
+ *
+ * Returns: the character encoding.
+ * Since: 3.14
+ */
+const GtkSourceEncoding *
+gtk_source_file_get_encoding (GtkSourceFile *file)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), NULL);
+
+       return file->priv->encoding;
+}
+
+void
+_gtk_source_file_set_newline_type (GtkSourceFile        *file,
+                                  GtkSourceNewlineType  newline_type)
+{
+       g_return_if_fail (GTK_SOURCE_IS_FILE (file));
+
+       if (file->priv->newline_type != newline_type)
+       {
+               file->priv->newline_type = newline_type;
+               g_object_notify (G_OBJECT (file), "newline-type");
+       }
+}
+
+/**
+ * gtk_source_file_get_newline_type:
+ * @file: a #GtkSourceFile.
+ *
+ * Returns: the newline type.
+ * Since: 3.14
+ */
+GtkSourceNewlineType
+gtk_source_file_get_newline_type (GtkSourceFile *file)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), GTK_SOURCE_NEWLINE_TYPE_DEFAULT);
+
+       return file->priv->newline_type;
+}
+
+void
+_gtk_source_file_set_compression_type (GtkSourceFile            *file,
+                                      GtkSourceCompressionType  compression_type)
+{
+       g_return_if_fail (GTK_SOURCE_IS_FILE (file));
+
+       if (file->priv->compression_type != compression_type)
+       {
+               file->priv->compression_type = compression_type;
+               g_object_notify (G_OBJECT (file), "compression-type");
+       }
+}
+
+/**
+ * gtk_source_file_get_compression_type:
+ * @file: a #GtkSourceFile.
+ *
+ * Returns: the compression type.
+ * Since: 3.14
+ */
+GtkSourceCompressionType
+gtk_source_file_get_compression_type (GtkSourceFile *file)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), GTK_SOURCE_COMPRESSION_TYPE_NONE);
+
+       return file->priv->compression_type;
+}
+
+/**
+ * gtk_source_file_set_mount_operation_factory:
+ * @file: a #GtkSourceFile.
+ * @callback: (scope notified): a #GtkSourceMountOperationFactory to call when a
+ *   #GMountOperation is needed.
+ * @user_data: (closure): the data to pass to the @callback function.
+ * @notify: (nullable): function to call on @user_data when the @callback is no
+ *   longer needed, or %NULL.
+ *
+ * Sets a #GtkSourceMountOperationFactory function that will be called when a
+ * #GMountOperation must be created. This is useful for creating a
+ * #GtkMountOperation with the parent #GtkWindow.
+ *
+ * If a mount operation factory isn't set, g_mount_operation_new() will be
+ * called.
+ *
+ * Since: 3.14
+ */
+void
+gtk_source_file_set_mount_operation_factory (GtkSourceFile                  *file,
+                                            GtkSourceMountOperationFactory  callback,
+                                            gpointer                        user_data,
+                                            GDestroyNotify                  notify)
+{
+       g_return_if_fail (GTK_SOURCE_IS_FILE (file));
+
+       if (file->priv->mount_operation_notify != NULL)
+       {
+               file->priv->mount_operation_notify (file->priv->mount_operation_userdata);
+       }
+
+       file->priv->mount_operation_factory = callback;
+       file->priv->mount_operation_userdata = user_data;
+       file->priv->mount_operation_notify = notify;
+}
+
+GMountOperation *
+_gtk_source_file_create_mount_operation (GtkSourceFile *file)
+{
+       return (file != NULL && file->priv->mount_operation_factory != NULL) ?
+               file->priv->mount_operation_factory (file, file->priv->mount_operation_userdata) :
+               g_mount_operation_new ();
+}
+
+gboolean
+_gtk_source_file_get_modification_time (GtkSourceFile *file,
+                                       GTimeVal      *modification_time)
+{
+       g_assert (modification_time != NULL);
+
+       if (file == NULL)
+       {
+               return FALSE;
+       }
+
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), FALSE);
+
+       if (file->priv->modification_time_set)
+       {
+               *modification_time = file->priv->modification_time;
+       }
+
+       return file->priv->modification_time_set;
+}
+
+void
+_gtk_source_file_set_modification_time (GtkSourceFile *file,
+                                       GTimeVal       modification_time)
+{
+       if (file != NULL)
+       {
+               g_return_if_fail (GTK_SOURCE_IS_FILE (file));
+
+               file->priv->modification_time = modification_time;
+               file->priv->modification_time_set = TRUE;
+       }
+}
diff --git a/gtksourceview/gtksourcefile.h b/gtksourceview/gtksourcefile.h
new file mode 100644
index 0000000..e4859f1
--- /dev/null
+++ b/gtksourceview/gtksourcefile.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* gtksourcefile.h
+ * This file is part of GtkSourceView
+ *
+ * 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_FILE_H__
+#define __GTK_SOURCE_FILE_H__
+
+#include <gtk/gtk.h>
+#include <gtksourceview/gtksourcetypes.h>
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_FILE             (gtk_source_file_get_type ())
+#define GTK_SOURCE_FILE(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_SOURCE_TYPE_FILE, 
GtkSourceFile))
+#define GTK_SOURCE_FILE_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_SOURCE_TYPE_FILE, 
GtkSourceFileClass))
+#define GTK_SOURCE_IS_FILE(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_SOURCE_TYPE_FILE))
+#define GTK_SOURCE_IS_FILE_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_SOURCE_TYPE_FILE))
+#define GTK_SOURCE_FILE_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_SOURCE_TYPE_FILE, 
GtkSourceFileClass))
+
+typedef struct _GtkSourceFileClass    GtkSourceFileClass;
+typedef struct _GtkSourceFilePrivate  GtkSourceFilePrivate;
+
+/**
+ * GtkSourceMountOperationFactory:
+ * @file: a #GtkSourceFile.
+ * @userdata: user data
+ *
+ * Type definition for a function that will be called to create a
+ * #GMountOperation. This is useful for creating a #GtkMountOperation.
+ *
+ * Since: 3.14
+ */
+typedef GMountOperation *(*GtkSourceMountOperationFactory) (GtkSourceFile *file,
+                                                           gpointer       userdata);
+
+struct _GtkSourceFile
+{
+       GObject parent;
+
+       GtkSourceFilePrivate *priv;
+};
+
+struct _GtkSourceFileClass
+{
+       GObjectClass parent_class;
+
+       gpointer padding[10];
+};
+
+GType           gtk_source_file_get_type                       (void) G_GNUC_CONST;
+
+GtkSourceFile  *gtk_source_file_new                            (void);
+
+GFile          *gtk_source_file_get_location                   (GtkSourceFile *file);
+
+void            gtk_source_file_set_location                   (GtkSourceFile *file,
+                                                                GFile         *location);
+
+const GtkSourceEncoding *
+                gtk_source_file_get_encoding                   (GtkSourceFile *file);
+
+GtkSourceNewlineType
+                gtk_source_file_get_newline_type               (GtkSourceFile *file);
+
+GtkSourceCompressionType
+                gtk_source_file_get_compression_type           (GtkSourceFile *file);
+
+void            gtk_source_file_set_mount_operation_factory    (GtkSourceFile                  *file,
+                                                                GtkSourceMountOperationFactory  callback,
+                                                                gpointer                        user_data,
+                                                                GDestroyNotify                  notify);
+
+G_GNUC_INTERNAL
+void            _gtk_source_file_set_encoding                  (GtkSourceFile           *file,
+                                                                const GtkSourceEncoding *encoding);
+
+G_GNUC_INTERNAL
+void            _gtk_source_file_set_newline_type              (GtkSourceFile        *file,
+                                                                GtkSourceNewlineType  newline_type);
+
+G_GNUC_INTERNAL
+void            _gtk_source_file_set_compression_type          (GtkSourceFile            *file,
+                                                                GtkSourceCompressionType  compression_type);
+
+G_GNUC_INTERNAL
+GMountOperation        *_gtk_source_file_create_mount_operation        (GtkSourceFile *file);
+
+G_GNUC_INTERNAL
+gboolean        _gtk_source_file_get_modification_time         (GtkSourceFile *file,
+                                                                GTimeVal      *modification_time);
+
+G_GNUC_INTERNAL
+void            _gtk_source_file_set_modification_time         (GtkSourceFile *file,
+                                                                GTimeVal       modification_time);
+
+G_END_DECLS
+
+#endif /* __GTK_SOURCE_FILE_H__ */
diff --git a/gtksourceview/gtksourcefileloader.c b/gtksourceview/gtksourcefileloader.c
new file mode 100644
index 0000000..08bcbc1
--- /dev/null
+++ b/gtksourceview/gtksourcefileloader.c
@@ -0,0 +1,1112 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* gtksourcefileloader.c
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ * Copyright (C) 2007 - Paolo Maggi, Steve Frécinaux
+ * Copyright (C) 2008 - Jesse van den Kieboom
+ * Copyright (C) 2014 - Sébastien Wilmet
+ *
+ * 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 "gtksourcefileloader.h"
+#include "gtksourcebuffer.h"
+#include "gtksourcefile.h"
+#include "gtksourcebufferoutputstream.h"
+#include "gtksourceencoding.h"
+#include "gtksourceview-typebuiltins.h"
+#include "gtksourceview-i18n.h"
+
+/**
+ * SECTION:fileloader
+ * @Short_description: Load a file into a GtkSourceBuffer
+ * @Title: GtkSourceFileLoader
+ * @See_also: #GtkSourceFile, #GtkSourceFileSaver
+ *
+ * A #GtkSourceFileLoader object permits to load the content of a #GFile or a
+ * #GInputStream into a #GtkSourceBuffer.
+ */
+
+#if 0
+#define DEBUG(x) (x)
+#else
+#define DEBUG(x)
+#endif
+
+enum
+{
+       PROP_0,
+       PROP_BUFFER,
+       PROP_FILE,
+       PROP_LOCATION,
+       PROP_INPUT_STREAM
+};
+
+#define READ_CHUNK_SIZE 8192
+#define LOADER_QUERY_ATTRIBUTES G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," \
+                               G_FILE_ATTRIBUTE_STANDARD_TYPE "," \
+                               G_FILE_ATTRIBUTE_TIME_MODIFIED "," \
+                               G_FILE_ATTRIBUTE_STANDARD_SIZE "," \
+                               G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE
+
+struct _GtkSourceFileLoaderPrivate
+{
+       /* Weak ref to the GtkSourceBuffer. A strong ref could create a
+        * reference cycle in an application. For example a subclass of
+        * GtkSourceBuffer can have a strong ref to the FileLoader.
+        */
+       GtkSourceBuffer *source_buffer;
+
+       /* Weak ref to the GtkSourceFile. A strong ref could create a reference
+        * cycle in an application. For example a subclass of GtkSourceFile can
+        * have a strong ref to the FileLoader.
+        */
+       GtkSourceFile *file;
+
+       GFile *location;
+
+       /* The value of the "input-stream" property. */
+       GInputStream *input_stream_property;
+
+       GSList *candidate_encodings;
+
+       GFileInfo *info;
+       const GtkSourceEncoding *auto_detected_encoding;
+       GtkSourceNewlineType auto_detected_newline_type;
+       GtkSourceCompressionType auto_detected_compression_type;
+
+       GTask *task;
+
+       goffset total_bytes_read;
+       goffset total_size;
+       GFileProgressCallback progress_cb;
+       gpointer progress_cb_data;
+       GDestroyNotify progress_cb_notify;
+
+       gchar chunk_buffer[READ_CHUNK_SIZE];
+       gssize chunk_bytes_read;
+
+       /* The two streams cannot be spliced directly, because
+        * (1) we need to call the progress callback
+        * (2) sync methods must be used for the output stream, and async
+        *     methods for the input stream.
+        */
+       GInputStream *input_stream;
+       GtkSourceBufferOutputStream *output_stream;
+
+       guint guess_content_type_from_content : 1;
+       guint tried_mount : 1;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GtkSourceFileLoader, gtk_source_file_loader, G_TYPE_OBJECT)
+
+static void open_file (GtkSourceFileLoader *loader);
+static void read_file_chunk (GtkSourceFileLoader *loader);
+
+static GtkSourceCompressionType
+get_compression_type_from_content_type (const gchar *content_type)
+{
+       if (content_type == NULL)
+       {
+               return GTK_SOURCE_COMPRESSION_TYPE_NONE;
+       }
+
+       if (g_content_type_is_a (content_type, "application/x-gzip"))
+       {
+               return GTK_SOURCE_COMPRESSION_TYPE_GZIP;
+       }
+
+       return GTK_SOURCE_COMPRESSION_TYPE_NONE;
+}
+
+static void
+gtk_source_file_loader_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+       GtkSourceFileLoader *loader = GTK_SOURCE_FILE_LOADER (object);
+
+       switch (prop_id)
+       {
+               case PROP_BUFFER:
+                       g_assert (loader->priv->source_buffer == NULL);
+                       loader->priv->source_buffer = g_value_get_object (value);
+                       g_object_add_weak_pointer (G_OBJECT (loader->priv->source_buffer),
+                                                  (gpointer *)&loader->priv->source_buffer);
+                       break;
+
+               case PROP_FILE:
+                       g_assert (loader->priv->file == NULL);
+                       loader->priv->file = g_value_get_object (value);
+                       g_object_add_weak_pointer (G_OBJECT (loader->priv->file),
+                                                  (gpointer *)&loader->priv->file);
+                       break;
+
+               case PROP_LOCATION:
+                       g_assert (loader->priv->location == NULL);
+                       loader->priv->location = g_value_dup_object (value);
+                       break;
+
+               case PROP_INPUT_STREAM:
+                       g_assert (loader->priv->input_stream_property == NULL);
+                       loader->priv->input_stream_property = g_value_dup_object (value);
+                       break;
+
+               default:
+                       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                       break;
+       }
+}
+
+static void
+gtk_source_file_loader_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+       GtkSourceFileLoader *loader = GTK_SOURCE_FILE_LOADER (object);
+
+       switch (prop_id)
+       {
+               case PROP_BUFFER:
+                       g_value_set_object (value, loader->priv->source_buffer);
+                       break;
+
+               case PROP_FILE:
+                       g_value_set_object (value, loader->priv->file);
+                       break;
+
+               case PROP_LOCATION:
+                       g_value_set_object (value, loader->priv->location);
+                       break;
+
+               case PROP_INPUT_STREAM:
+                       g_value_set_object (value, loader->priv->input_stream_property);
+                       break;
+
+               default:
+                       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                       break;
+       }
+}
+
+static void
+reset (GtkSourceFileLoader *loader)
+{
+       g_clear_object (&loader->priv->task);
+       g_clear_object (&loader->priv->input_stream);
+       g_clear_object (&loader->priv->output_stream);
+       g_clear_object (&loader->priv->info);
+
+       if (loader->priv->progress_cb_notify != NULL)
+       {
+               loader->priv->progress_cb_notify (loader->priv->progress_cb_data);
+               loader->priv->progress_cb_notify = NULL;
+       }
+
+       loader->priv->progress_cb = NULL;
+       loader->priv->progress_cb_data = NULL;
+}
+
+static void
+gtk_source_file_loader_dispose (GObject *object)
+{
+       GtkSourceFileLoader *loader = GTK_SOURCE_FILE_LOADER (object);
+
+       reset (loader);
+
+       if (loader->priv->source_buffer != NULL)
+       {
+               g_object_remove_weak_pointer (G_OBJECT (loader->priv->source_buffer),
+                                             (gpointer *)&loader->priv->source_buffer);
+
+               loader->priv->source_buffer = NULL;
+       }
+
+       if (loader->priv->file != NULL)
+       {
+               g_object_remove_weak_pointer (G_OBJECT (loader->priv->file),
+                                             (gpointer *)&loader->priv->file);
+
+               loader->priv->file = NULL;
+       }
+
+       g_clear_object (&loader->priv->location);
+       g_clear_object (&loader->priv->input_stream_property);
+
+       g_slist_free (loader->priv->candidate_encodings);
+       loader->priv->candidate_encodings = NULL;
+
+       G_OBJECT_CLASS (gtk_source_file_loader_parent_class)->dispose (object);
+}
+
+static void
+gtk_source_file_loader_constructed (GObject *object)
+{
+       GtkSourceFileLoader *loader = GTK_SOURCE_FILE_LOADER (object);
+
+       if (loader->priv->file != NULL)
+       {
+               const GtkSourceEncoding *encoding;
+
+               encoding = gtk_source_file_get_encoding (loader->priv->file);
+
+               g_slist_free (loader->priv->candidate_encodings);
+               loader->priv->candidate_encodings = g_slist_prepend (NULL, (gpointer) encoding);
+       }
+
+       G_OBJECT_CLASS (gtk_source_file_loader_parent_class)->dispose (object);
+}
+
+static void
+gtk_source_file_loader_class_init (GtkSourceFileLoaderClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->dispose = gtk_source_file_loader_dispose;
+       object_class->set_property = gtk_source_file_loader_set_property;
+       object_class->get_property = gtk_source_file_loader_get_property;
+       object_class->constructed = gtk_source_file_loader_constructed;
+
+       /**
+        * GtkSourceFileLoader:buffer:
+        *
+        * The #GtkSourceBuffer to load the contents into. The
+        * #GtkSourceFileLoader object has a weak reference to the :buffer.
+        *
+        * Since: 3.14
+        */
+       g_object_class_install_property (object_class, PROP_BUFFER,
+                                        g_param_spec_object ("buffer",
+                                                             "GtkSourceBuffer",
+                                                             "",
+                                                             GTK_SOURCE_TYPE_BUFFER,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY |
+                                                             G_PARAM_STATIC_STRINGS));
+
+       /**
+        * GtkSourceFileLoader:file:
+        *
+        * The #GtkSourceFile. The #GtkSourceFileLoader object has a weak
+        * reference to the :file.
+        *
+        * Since: 3.14
+        */
+       g_object_class_install_property (object_class, PROP_FILE,
+                                        g_param_spec_object ("file",
+                                                             "GtkSourceFile",
+                                                             "",
+                                                             GTK_SOURCE_TYPE_FILE,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY |
+                                                             G_PARAM_STATIC_STRINGS));
+
+       /**
+        * GtkSourceFileLoader:location:
+        *
+        * The #GFile to load.
+        *
+        * Since: 3.14
+        */
+       g_object_class_install_property (object_class, PROP_LOCATION,
+                                        g_param_spec_object ("location",
+                                                             _("Location"),
+                                                             "",
+                                                             G_TYPE_FILE,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY |
+                                                             G_PARAM_STATIC_STRINGS));
+
+       /**
+        * GtkSourceFileLoader:input-stream:
+        *
+        * The #GInputStream to load. Useful for reading stdin. If this property
+        * is set, the #GtkSourceFileLoader:location property is ignored.
+        *
+        * Since: 3.14
+        */
+       g_object_class_install_property (object_class, PROP_INPUT_STREAM,
+                                        g_param_spec_object ("input-stream",
+                                                             _("Input stream"),
+                                                             "",
+                                                             G_TYPE_INPUT_STREAM,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY |
+                                                             G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gtk_source_file_loader_init (GtkSourceFileLoader *loader)
+{
+       loader->priv = gtk_source_file_loader_get_instance_private (loader);
+}
+
+static void
+close_input_stream_cb (GInputStream        *input_stream,
+                      GAsyncResult        *result,
+                      GtkSourceFileLoader *loader)
+{
+       GError *error = NULL;
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       g_input_stream_close_finish (input_stream, result, &error);
+
+       if (error != NULL)
+       {
+               DEBUG ({
+                      g_print ("Closing input stream error: %s\n", error->message);
+               });
+
+               g_task_return_error (loader->priv->task, error);
+               return;
+       }
+
+       DEBUG ({
+              g_print ("Close output stream\n");
+       });
+
+       g_output_stream_close (G_OUTPUT_STREAM (loader->priv->output_stream),
+                              g_task_get_cancellable (loader->priv->task),
+                              &error);
+
+       if (error != NULL)
+       {
+               g_task_return_error (loader->priv->task, error);
+               return;
+       }
+
+       /* Check if we needed some fallback char, if so, check if there was a
+        * previous error and if not set a fallback used error.
+        */
+       if (gtk_source_buffer_output_stream_get_num_fallbacks (loader->priv->output_stream) > 0)
+       {
+               g_task_return_new_error (loader->priv->task,
+                                        GTK_SOURCE_FILE_LOADER_ERROR,
+                                        GTK_SOURCE_FILE_LOADER_ERROR_CONVERSION_FALLBACK,
+                                        "There was an encoding conversion error and it was "
+                                        "needed to use a fallback character");
+               return;
+       }
+
+       g_task_return_boolean (loader->priv->task, TRUE);
+}
+
+static void
+write_complete (GtkSourceFileLoader *loader)
+{
+       g_input_stream_close_async (loader->priv->input_stream,
+                                   g_task_get_priority (loader->priv->task),
+                                   g_task_get_cancellable (loader->priv->task),
+                                   (GAsyncReadyCallback) close_input_stream_cb,
+                                   loader);
+}
+
+static void
+write_file_chunk (GtkSourceFileLoader *loader)
+{
+       gssize chunk_bytes_written = 0;
+
+       while (chunk_bytes_written < loader->priv->chunk_bytes_read)
+       {
+               gssize bytes_written;
+               GError *error = NULL;
+
+               /* We use sync methods on the buffer stream since it is in memory. Using
+                * async would be racy and we can end up with invalidated iters.
+                */
+               bytes_written = g_output_stream_write (G_OUTPUT_STREAM (loader->priv->output_stream),
+                                                      loader->priv->chunk_buffer + chunk_bytes_written,
+                                                      loader->priv->chunk_bytes_read - chunk_bytes_written,
+                                                      g_task_get_cancellable (loader->priv->task),
+                                                      &error);
+
+               DEBUG ({
+                      g_print ("Written: %" G_GSSIZE_FORMAT "\n", bytes_written);
+               });
+
+               if (error != NULL)
+               {
+                       DEBUG ({
+                              g_print ("Write error: %s\n", error->message);
+                       });
+
+                       g_task_return_error (loader->priv->task, error);
+                       return;
+               }
+
+               chunk_bytes_written += bytes_written;
+       }
+
+       /* FIXME: note that calling the progress callback blocks the read...
+        * Check if it isn't a performance problem.
+        */
+       if (loader->priv->progress_cb != NULL && loader->priv->total_size > 0)
+       {
+               loader->priv->progress_cb (loader->priv->total_bytes_read,
+                                          loader->priv->total_size,
+                                          loader->priv->progress_cb_data);
+       }
+
+       read_file_chunk (loader);
+}
+
+static void
+read_cb (GInputStream        *input_stream,
+        GAsyncResult        *result,
+        GtkSourceFileLoader *loader)
+{
+       GError *error = NULL;
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       loader->priv->chunk_bytes_read = g_input_stream_read_finish (input_stream, result, &error);
+
+       if (error != NULL)
+       {
+               g_task_return_error (loader->priv->task, error);
+               return;
+       }
+
+       /* Check for the extremely unlikely case where the file size overflows. */
+       if (loader->priv->total_bytes_read + loader->priv->chunk_bytes_read < loader->priv->total_bytes_read)
+       {
+               g_task_return_new_error (loader->priv->task,
+                                        GTK_SOURCE_FILE_LOADER_ERROR,
+                                        GTK_SOURCE_FILE_LOADER_ERROR_TOO_BIG,
+                                        "File too big");
+               return;
+       }
+
+       if (loader->priv->guess_content_type_from_content &&
+           loader->priv->chunk_bytes_read > 0 &&
+           loader->priv->total_bytes_read == 0)
+       {
+               gchar *guessed;
+
+               guessed = g_content_type_guess (NULL,
+                                               (guchar *)loader->priv->chunk_buffer,
+                                               loader->priv->chunk_bytes_read,
+                                               NULL);
+
+               if (guessed != NULL)
+               {
+                       g_file_info_set_attribute_string (loader->priv->info,
+                                                         G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
+                                                         guessed);
+
+                       g_free (guessed);
+               }
+       }
+
+       /* Bump the size. */
+       loader->priv->total_bytes_read += loader->priv->chunk_bytes_read;
+
+       /* End of the file, we are done! */
+       if (loader->priv->chunk_bytes_read == 0)
+       {
+               /* Flush the stream to ensure proper line ending detection. */
+               g_output_stream_flush (G_OUTPUT_STREAM (loader->priv->output_stream), NULL, NULL);
+
+               loader->priv->auto_detected_encoding =
+                       gtk_source_buffer_output_stream_get_guessed (loader->priv->output_stream);
+
+               loader->priv->auto_detected_newline_type =
+                       gtk_source_buffer_output_stream_detect_newline_type (loader->priv->output_stream);
+
+               write_complete (loader);
+               return;
+       }
+
+       write_file_chunk (loader);
+}
+
+static void
+read_file_chunk (GtkSourceFileLoader *loader)
+{
+       g_input_stream_read_async (loader->priv->input_stream,
+                                  loader->priv->chunk_buffer,
+                                  READ_CHUNK_SIZE,
+                                  g_task_get_priority (loader->priv->task),
+                                  g_task_get_cancellable (loader->priv->task),
+                                  (GAsyncReadyCallback) read_cb,
+                                  loader);
+}
+
+static void
+add_gzip_decompressor_stream (GtkSourceFileLoader *loader)
+{
+       GZlibDecompressor *decompressor;
+       GInputStream *new_input_stream;
+
+       decompressor = g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP);
+
+       new_input_stream = g_converter_input_stream_new (loader->priv->input_stream,
+                                                        G_CONVERTER (decompressor));
+
+       g_object_unref (loader->priv->input_stream);
+       g_object_unref (decompressor);
+
+       loader->priv->input_stream = new_input_stream;
+}
+
+static void
+create_input_stream (GtkSourceFileLoader *loader)
+{
+       loader->priv->auto_detected_compression_type = GTK_SOURCE_COMPRESSION_TYPE_NONE;
+
+       if (loader->priv->input_stream_property != NULL)
+       {
+               loader->priv->input_stream = g_object_ref (loader->priv->input_stream_property);
+       }
+       else if (g_file_info_has_attribute (loader->priv->info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE))
+       {
+               const gchar *content_type = g_file_info_get_content_type (loader->priv->info);
+
+               switch (get_compression_type_from_content_type (content_type))
+               {
+                       case GTK_SOURCE_COMPRESSION_TYPE_GZIP:
+                               add_gzip_decompressor_stream (loader);
+                               loader->priv->auto_detected_compression_type = 
GTK_SOURCE_COMPRESSION_TYPE_GZIP;
+                               break;
+
+                       case GTK_SOURCE_COMPRESSION_TYPE_NONE:
+                               /* NOOP */
+                               break;
+               }
+       }
+
+       g_return_if_fail (loader->priv->input_stream != NULL);
+
+       /* start reading */
+       read_file_chunk (loader);
+}
+
+static void
+query_info_cb (GFile               *file,
+              GAsyncResult        *result,
+              GtkSourceFileLoader *loader)
+{
+       GError *error = NULL;
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       loader->priv->info = g_file_query_info_finish (file, result, &error);
+
+       if (error != NULL)
+       {
+               g_task_return_error (loader->priv->task, error);
+               return;
+       }
+
+       if (g_file_info_has_attribute (loader->priv->info, G_FILE_ATTRIBUTE_STANDARD_TYPE) &&
+           g_file_info_get_file_type (loader->priv->info) != G_FILE_TYPE_REGULAR)
+       {
+               g_task_return_new_error (loader->priv->task,
+                                        G_IO_ERROR,
+                                        G_IO_ERROR_NOT_REGULAR_FILE,
+                                        "Not a regular file");
+               return;
+       }
+
+       if (g_file_info_has_attribute (loader->priv->info, G_FILE_ATTRIBUTE_STANDARD_SIZE))
+       {
+               loader->priv->total_size = g_file_info_get_attribute_uint64 (loader->priv->info,
+                                                                            G_FILE_ATTRIBUTE_STANDARD_SIZE);
+       }
+
+       create_input_stream (loader);
+}
+
+static void
+mount_cb (GFile               *file,
+         GAsyncResult        *result,
+         GtkSourceFileLoader *loader)
+{
+       GError *error = NULL;
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       g_file_mount_enclosing_volume_finish (file, result, &error);
+
+       if (error != NULL)
+       {
+               g_task_return_error (loader->priv->task, error);
+       }
+       else
+       {
+               /* Try again to open the file for reading. */
+               open_file (loader);
+       }
+}
+
+static void
+recover_not_mounted (GtkSourceFileLoader *loader)
+{
+       GMountOperation *mount_operation;
+
+       mount_operation = _gtk_source_file_create_mount_operation (loader->priv->file);
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       loader->priv->tried_mount = TRUE;
+
+       g_file_mount_enclosing_volume (loader->priv->location,
+                                      G_MOUNT_MOUNT_NONE,
+                                      mount_operation,
+                                      g_task_get_cancellable (loader->priv->task),
+                                      (GAsyncReadyCallback) mount_cb,
+                                      loader);
+
+       g_object_unref (mount_operation);
+}
+
+static void
+open_file_cb (GFile               *file,
+             GAsyncResult        *result,
+             GtkSourceFileLoader *loader)
+{
+       GError *error = NULL;
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       loader->priv->input_stream = G_INPUT_STREAM (g_file_read_finish (file, result, &error));
+
+       if (error != NULL)
+       {
+               if (error->code == G_IO_ERROR_NOT_MOUNTED && !loader->priv->tried_mount)
+               {
+                       recover_not_mounted (loader);
+                       g_error_free (error);
+                       return;
+               }
+
+               g_task_return_error (loader->priv->task, error);
+               return;
+       }
+
+       /* Get the file info: note we cannot use
+        * g_file_input_stream_query_info_async since it is not able to get the
+        * content type etc, beside it is not supported by gvfs.
+        * Using the file instead of the stream is slightly racy, but for
+        * loading this is not too bad...
+        */
+       g_file_query_info_async (file,
+                                LOADER_QUERY_ATTRIBUTES,
+                                 G_FILE_QUERY_INFO_NONE,
+                                g_task_get_priority (loader->priv->task),
+                                g_task_get_cancellable (loader->priv->task),
+                                (GAsyncReadyCallback) query_info_cb,
+                                loader);
+}
+
+static void
+open_file (GtkSourceFileLoader *loader)
+{
+       g_file_read_async (loader->priv->location,
+                          g_task_get_priority (loader->priv->task),
+                          g_task_get_cancellable (loader->priv->task),
+                          (GAsyncReadyCallback) open_file_cb,
+                          loader);
+}
+
+GQuark
+gtk_source_file_loader_error_quark (void)
+{
+       static GQuark quark = 0;
+
+       if (G_UNLIKELY (quark == 0))
+       {
+               quark = g_quark_from_static_string ("gtk-source-file-loader-error");
+       }
+
+       return quark;
+}
+
+/**
+ * gtk_source_file_loader_new:
+ * @buffer: the #GtkSourceBuffer to load the contents into.
+ * @file: the #GtkSourceFile.
+ * @location: the #GFile to load.
+ *
+ * Returns: a new #GtkSourceFileLoader object.
+ * Since: 3.14
+ */
+GtkSourceFileLoader *
+gtk_source_file_loader_new (GtkSourceBuffer *buffer,
+                           GtkSourceFile   *file,
+                           GFile           *location)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL);
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), NULL);
+       g_return_val_if_fail (G_IS_FILE (location), NULL);
+
+       return g_object_new (GTK_SOURCE_TYPE_FILE_LOADER,
+                            "buffer", buffer,
+                            "file", file,
+                            "location", location,
+                            NULL);
+}
+
+/**
+ * gtk_source_file_loader_new_from_stream:
+ * @buffer: the #GtkSourceBuffer to load the contents into.
+ * @file: the #GtkSourceFile.
+ * @stream: the #GInputStream to load, e.g. stdin.
+ *
+ * Returns: a new #GtkSourceFileLoader object.
+ * Since: 3.14
+ */
+GtkSourceFileLoader *
+gtk_source_file_loader_new_from_stream (GtkSourceBuffer *buffer,
+                                       GtkSourceFile   *file,
+                                       GInputStream    *stream)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL);
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), NULL);
+       g_return_val_if_fail (G_IS_INPUT_STREAM (stream), NULL);
+
+       return g_object_new (GTK_SOURCE_TYPE_FILE_LOADER,
+                            "buffer", buffer,
+                            "file", file,
+                            "input-stream", stream,
+                            NULL);
+}
+
+/**
+ * gtk_source_file_loader_set_candidate_encodings:
+ * @loader: a #GtkSourceFileLoader.
+ * @candidate_encodings: (element-type GtkSourceEncoding): a list of
+ *   #GtkSourceEncoding<!-- -->s.
+ *
+ * Sets the candidate encodings for the file loading. The encodings are tried in
+ * the same order as the list.
+ *
+ * There is by default only one candidate encoding, the #GtkSourceBuffer's
+ * encoding.
+ *
+ * Since: 3.14
+ */
+void
+gtk_source_file_loader_set_candidate_encodings (GtkSourceFileLoader *loader,
+                                               GSList              *candidate_encodings)
+{
+       g_slist_free (loader->priv->candidate_encodings);
+       loader->priv->candidate_encodings = g_slist_copy (candidate_encodings);
+}
+
+/**
+ * gtk_source_file_loader_get_buffer:
+ * @loader: a #GtkSourceFileLoader.
+ *
+ * Returns: (transfer none): the #GtkSourceBuffer to load the contents into.
+ * Since: 3.14
+ */
+GtkSourceBuffer *
+gtk_source_file_loader_get_buffer (GtkSourceFileLoader *loader)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE_LOADER (loader), NULL);
+
+       return loader->priv->source_buffer;
+}
+
+/**
+ * gtk_source_file_loader_get_file:
+ * @loader: a #GtkSourceFileLoader.
+ *
+ * Returns: (transfer none): the #GtkSourceFile.
+ * Since: 3.14
+ */
+GtkSourceFile *
+gtk_source_file_loader_get_file (GtkSourceFileLoader *loader)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE_LOADER (loader), NULL);
+
+       return loader->priv->file;
+}
+
+/**
+ * gtk_source_file_loader_get_location:
+ * @loader: a #GtkSourceFileLoader.
+ *
+ * Returns: (transfer none): the #GFile to load, or %NULL if an input stream is
+ *   used.
+ * Since: 3.14
+ */
+GFile *
+gtk_source_file_loader_get_location (GtkSourceFileLoader *loader)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE_LOADER (loader), NULL);
+
+       return loader->priv->location;
+}
+
+/**
+ * gtk_source_file_loader_get_input_stream:
+ * @loader: a #GtkSourceFileLoader.
+ *
+ * Returns: (transfer none): the #GInputStream to load, or %NULL if a #GFile is
+ *   used.
+ * Since: 3.14
+ */
+GInputStream *
+gtk_source_file_loader_get_input_stream (GtkSourceFileLoader *loader)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE_LOADER (loader), NULL);
+
+       return loader->priv->input_stream_property;
+}
+
+/**
+ * gtk_source_file_loader_load_async:
+ * @loader: a #GtkSourceFileLoader.
+ * @io_priority: the I/O priority of the request. E.g. %G_PRIORITY_LOW,
+ *   %G_PRIORITY_DEFAULT or %G_PRIORITY_HIGH.
+ * @cancellable: (nullable): optional #GCancellable object, %NULL to ignore.
+ * @progress_callback: (scope notified) (nullable): function to call back with
+ *   progress information, or %NULL if progress information is not needed.
+ * @progress_callback_data: (closure): user data to pass to @progress_callback.
+ * @progress_callback_notify: (nullable): function to call on
+ *   @progress_callback_data when the @progress_callback is no longer needed, or
+ *   %NULL.
+ * @callback: (scope async): a #GAsyncReadyCallback to call when the request is
+ *   satisfied.
+ * @user_data: user data to pass to @callback.
+ *
+ * Loads asynchronously the file or input stream contents into the
+ * #GtkSourceBuffer. See the #GAsyncResult documentation to know how to use this
+ * function.
+ *
+ * Since: 3.14
+ */
+
+/* The GDestroyNotify is needed, currently the following bug is not fixed:
+ * https://bugzilla.gnome.org/show_bug.cgi?id=616044
+ */
+void
+gtk_source_file_loader_load_async (GtkSourceFileLoader   *loader,
+                                  gint                   io_priority,
+                                  GCancellable          *cancellable,
+                                  GFileProgressCallback  progress_callback,
+                                  gpointer               progress_callback_data,
+                                  GDestroyNotify         progress_callback_notify,
+                                  GAsyncReadyCallback    callback,
+                                  gpointer               user_data)
+{
+       gboolean implicit_trailing_newline;
+
+       g_return_if_fail (GTK_SOURCE_IS_FILE_LOADER (loader));
+       g_return_if_fail (loader->priv->task == NULL);
+
+       if (loader->priv->source_buffer == NULL ||
+           loader->priv->file == NULL)
+       {
+               return;
+       }
+
+       reset (loader);
+
+       loader->priv->task = g_task_new (loader, cancellable, callback, user_data);
+       g_task_set_priority (loader->priv->task, io_priority);
+
+       loader->priv->progress_cb = progress_callback;
+       loader->priv->progress_cb_data = progress_callback_data;
+       loader->priv->progress_cb_notify = progress_callback_notify;
+
+       DEBUG ({
+              g_print ("Start loading\n");
+       });
+
+       /* Update GtkSourceFile location directly. The other GtkSourceFile
+        * properties are updated when the operation is finished. But since the
+        * file is loaded, the previous contents is lost, so the previous
+        * location is anyway not needed. And for display purposes, the new
+        * location is directly needed (for example to display the filename in a
+        * tab or an info bar with the progress information).
+        */
+       if (loader->priv->input_stream_property != NULL)
+       {
+               gtk_source_file_set_location (loader->priv->file, NULL);
+       }
+       else
+       {
+               gtk_source_file_set_location (loader->priv->file,
+                                             loader->priv->location);
+       }
+
+       implicit_trailing_newline = gtk_source_buffer_get_implicit_trailing_newline 
(loader->priv->source_buffer);
+
+       /* The BufferOutputStream has a strong reference to the buffer.
+         * We create the BufferOutputStream here so we are sure that the
+         * buffer will not be destroyed during the file loading.
+         */
+       loader->priv->output_stream = gtk_source_buffer_output_stream_new (loader->priv->source_buffer,
+                                                                          loader->priv->candidate_encodings,
+                                                                          implicit_trailing_newline);
+
+       if (loader->priv->input_stream_property != NULL)
+       {
+               loader->priv->guess_content_type_from_content = TRUE;
+               loader->priv->info = g_file_info_new ();
+
+               create_input_stream (loader);
+       }
+       else
+       {
+               open_file (loader);
+       }
+}
+
+/**
+ * gtk_source_file_loader_load_finish:
+ * @loader: a #GtkSourceFileLoader.
+ * @result: a #GAsyncResult.
+ * @error: a #GError, or %NULL.
+ *
+ * Finishes a file loading started with gtk_source_file_loader_load_async().
+ *
+ * If the contents has been loaded, the following #GtkSourceFile properties will
+ * be updated: the location, the encoding, the newline type and the compression
+ * type.
+ *
+ * Returns: whether the contents has been loaded successfully.
+ * Since: 3.14
+ */
+gboolean
+gtk_source_file_loader_load_finish (GtkSourceFileLoader  *loader,
+                                   GAsyncResult         *result,
+                                   GError              **error)
+{
+       gboolean ok;
+       gboolean update_buffer_properties;
+       GError *real_error = NULL;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE_LOADER (loader), FALSE);
+       g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+       g_return_val_if_fail (g_task_is_valid (result, loader), FALSE);
+
+       ok = g_task_propagate_boolean (G_TASK (result), &real_error);
+
+       if (error != NULL && real_error != NULL)
+       {
+               *error = g_error_copy (real_error);
+       }
+
+       /* Update the file properties if the contents has been loaded. The
+        * contents can be loaded successfully, or there can be encoding
+        * conversion errors with fallback characters. In the latter case, the
+        * encoding may be wrong, but since the contents has anyway be loaded,
+        * the file properties must be updated.
+        * With the other errors, normally the contents hasn't been loaded into
+        * the buffer, i.e. the buffer is still empty.
+        */
+       update_buffer_properties = ok || (real_error != NULL &&
+                                         real_error->domain == GTK_SOURCE_FILE_LOADER_ERROR &&
+                                         real_error->code == 
GTK_SOURCE_FILE_LOADER_ERROR_CONVERSION_FALLBACK);
+
+       if (update_buffer_properties && loader->priv->file != NULL)
+       {
+               /* The location is already updated at the beginning of the
+                * operation.
+                */
+
+               _gtk_source_file_set_encoding (loader->priv->file,
+                                              loader->priv->auto_detected_encoding);
+
+               _gtk_source_file_set_newline_type (loader->priv->file,
+                                                  loader->priv->auto_detected_newline_type);
+
+               _gtk_source_file_set_compression_type (loader->priv->file,
+                                                      loader->priv->auto_detected_compression_type);
+
+               if (g_file_info_has_attribute (loader->priv->info, G_FILE_ATTRIBUTE_TIME_MODIFIED))
+               {
+                       GTimeVal modification_time;
+                       g_file_info_get_modification_time (loader->priv->info, &modification_time);
+                       _gtk_source_file_set_modification_time (loader->priv->file, modification_time);
+               }
+       }
+
+       reset (loader);
+
+       if (real_error != NULL)
+       {
+               g_error_free (real_error);
+       }
+
+       return ok;
+}
+
+/**
+ * gtk_source_file_loader_get_encoding:
+ * @loader: a #GtkSourceFileLoader.
+ *
+ * Returns: the detected file encoding.
+ * Since: 3.14
+ */
+const GtkSourceEncoding *
+gtk_source_file_loader_get_encoding (GtkSourceFileLoader *loader)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE_LOADER (loader), NULL);
+
+       return loader->priv->auto_detected_encoding;
+}
+
+/**
+ * gtk_source_file_loader_get_newline_type:
+ * @loader: a #GtkSourceFileLoader.
+ *
+ * Returns: the detected newline type.
+ * Since: 3.14
+ */
+GtkSourceNewlineType
+gtk_source_file_loader_get_newline_type (GtkSourceFileLoader *loader)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE_LOADER (loader),
+                             GTK_SOURCE_NEWLINE_TYPE_LF);
+
+       return loader->priv->auto_detected_newline_type;
+}
+
+/**
+ * gtk_source_file_loader_get_compression_type:
+ * @loader: a #GtkSourceFileLoader.
+ *
+ * Returns: the detected compression type.
+ * Since: 3.14
+ */
+GtkSourceCompressionType
+gtk_source_file_loader_get_compression_type (GtkSourceFileLoader *loader)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE_LOADER (loader),
+                             GTK_SOURCE_COMPRESSION_TYPE_NONE);
+
+       return loader->priv->auto_detected_compression_type;
+}
diff --git a/gtksourceview/gtksourcefileloader.h b/gtksourceview/gtksourcefileloader.h
new file mode 100644
index 0000000..2ca1167
--- /dev/null
+++ b/gtksourceview/gtksourcefileloader.h
@@ -0,0 +1,123 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* gtksourcefileloader.h
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ * Copyright (C) 2007 - Paolo Maggi, Steve Frécinaux
+ * Copyright (C) 2008 - Jesse van den Kieboom
+ * Copyright (C) 2014 - Sébastien Wilmet
+ *
+ * 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_FILE_LOADER_H__
+#define __GTK_SOURCE_FILE_LOADER_H__
+
+#include <gtk/gtk.h>
+#include <gtksourceview/gtksourcetypes.h>
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_FILE_LOADER              (gtk_source_file_loader_get_type())
+#define GTK_SOURCE_FILE_LOADER(obj)              (G_TYPE_CHECK_INSTANCE_CAST((obj), 
GTK_SOURCE_TYPE_FILE_LOADER, GtkSourceFileLoader))
+#define GTK_SOURCE_FILE_LOADER_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST((klass), 
GTK_SOURCE_TYPE_FILE_LOADER, GtkSourceFileLoaderClass))
+#define GTK_SOURCE_IS_FILE_LOADER(obj)           (G_TYPE_CHECK_INSTANCE_TYPE((obj), 
GTK_SOURCE_TYPE_FILE_LOADER))
+#define GTK_SOURCE_IS_FILE_LOADER_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), 
GTK_SOURCE_TYPE_FILE_LOADER))
+#define GTK_SOURCE_FILE_LOADER_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS((obj), 
GTK_SOURCE_TYPE_FILE_LOADER, GtkSourceFileLoaderClass))
+
+typedef struct _GtkSourceFileLoaderClass   GtkSourceFileLoaderClass;
+typedef struct _GtkSourceFileLoaderPrivate GtkSourceFileLoaderPrivate;
+
+#define GTK_SOURCE_FILE_LOADER_ERROR gtk_source_file_loader_error_quark ()
+
+/**
+ * GtkSourceFileLoaderError:
+ * @GTK_SOURCE_FILE_LOADER_ERROR_TOO_BIG: The file is too big.
+ * @GTK_SOURCE_FILE_LOADER_ERROR_ENCODING_AUTO_DETECTION_FAILED: It is not
+ * possible to detect the encoding automatically.
+ * @GTK_SOURCE_FILE_LOADER_ERROR_CONVERSION_FALLBACK: There was an encoding
+ * conversion error and it was needed to use a fallback character.
+ *
+ * An error code used with the %GTK_SOURCE_FILE_LOADER_ERROR domain.
+ */
+typedef enum
+{
+       GTK_SOURCE_FILE_LOADER_ERROR_TOO_BIG,
+       GTK_SOURCE_FILE_LOADER_ERROR_ENCODING_AUTO_DETECTION_FAILED,
+       GTK_SOURCE_FILE_LOADER_ERROR_CONVERSION_FALLBACK
+} GtkSourceFileLoaderError;
+
+struct _GtkSourceFileLoader
+{
+       GObject parent;
+
+       GtkSourceFileLoaderPrivate *priv;
+};
+
+struct _GtkSourceFileLoaderClass
+{
+       GObjectClass parent_class;
+
+       gpointer padding[10];
+};
+
+GType                   gtk_source_file_loader_get_type        (void) G_GNUC_CONST;
+
+GQuark                  gtk_source_file_loader_error_quark     (void);
+
+GtkSourceFileLoader    *gtk_source_file_loader_new             (GtkSourceBuffer         *buffer,
+                                                                GtkSourceFile           *file,
+                                                                GFile                   *location);
+
+GtkSourceFileLoader    *gtk_source_file_loader_new_from_stream (GtkSourceBuffer         *buffer,
+                                                                GtkSourceFile           *file,
+                                                                GInputStream            *stream);
+
+void                    gtk_source_file_loader_set_candidate_encodings
+                                                               (GtkSourceFileLoader     *loader,
+                                                                GSList                  
*candidate_encodings);
+
+GtkSourceBuffer                *gtk_source_file_loader_get_buffer      (GtkSourceFileLoader     *loader);
+
+GtkSourceFile          *gtk_source_file_loader_get_file        (GtkSourceFileLoader     *loader);
+
+GFile                  *gtk_source_file_loader_get_location    (GtkSourceFileLoader     *loader);
+
+GInputStream           *gtk_source_file_loader_get_input_stream
+                                                               (GtkSourceFileLoader     *loader);
+
+void                    gtk_source_file_loader_load_async      (GtkSourceFileLoader     *loader,
+                                                                gint                     io_priority,
+                                                                GCancellable            *cancellable,
+                                                                GFileProgressCallback    progress_callback,
+                                                                gpointer                 
progress_callback_data,
+                                                                GDestroyNotify           
progress_callback_notify,
+                                                                GAsyncReadyCallback      callback,
+                                                                gpointer                 user_data);
+
+gboolean                gtk_source_file_loader_load_finish     (GtkSourceFileLoader     *loader,
+                                                                GAsyncResult            *result,
+                                                                GError                 **error);
+
+const GtkSourceEncoding        *gtk_source_file_loader_get_encoding    (GtkSourceFileLoader     *loader);
+
+GtkSourceNewlineType    gtk_source_file_loader_get_newline_type (GtkSourceFileLoader    *loader);
+
+GtkSourceCompressionType gtk_source_file_loader_get_compression_type
+                                                               (GtkSourceFileLoader     *loader);
+
+G_END_DECLS
+
+#endif  /* __GTK_SOURCE_FILE_LOADER_H__  */
diff --git a/gtksourceview/gtksourcefilesaver.c b/gtksourceview/gtksourcefilesaver.c
new file mode 100644
index 0000000..115ea7d
--- /dev/null
+++ b/gtksourceview/gtksourcefilesaver.c
@@ -0,0 +1,1345 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* gtksourcefilesaver.c
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2005-2007 - Paolo Borelli and Paolo Maggi
+ * Copyright (C) 2007 - Steve Frécinaux
+ * Copyright (C) 2008 - Jesse van den Kieboom
+ * Copyright (C) 2014 - Sébastien Wilmet
+ *
+ * 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 "gtksourcefilesaver.h"
+#include "gtksourcefile.h"
+#include "gtksourcebufferinputstream.h"
+#include "gtksourceencoding.h"
+#include "gtksourcebuffer.h"
+#include "gtksourcebuffer-private.h"
+#include "gtksourceview-typebuiltins.h"
+#include "gtksourceview-i18n.h"
+
+/**
+ * SECTION:filesaver
+ * @Short_description: Save a GtkSourceBuffer into a file
+ * @Title: GtkSourceFileSaver
+ * @See_also: #GtkSourceFile, #GtkSourceFileLoader
+ *
+ * A #GtkSourceFileSaver object permits to save a #GtkSourceBuffer into a
+ * #GFile.
+ */
+
+/* The code has been written initially in gedit (GeditDocumentSaver).
+ * It uses a GtkSourceBufferInputStream as input, create converter(s) if needed
+ * for the encoding and the compression, and write the contents to a
+ * GOutputStream (the file).
+ */
+
+#if 0
+#define DEBUG(x) (x)
+#else
+#define DEBUG(x)
+#endif
+
+#define WRITE_CHUNK_SIZE 8192
+
+#define QUERY_ATTRIBUTES G_FILE_ATTRIBUTE_TIME_MODIFIED
+
+enum
+{
+       PROP_0,
+       PROP_BUFFER,
+       PROP_FILE,
+       PROP_LOCATION,
+       PROP_ENCODING,
+       PROP_NEWLINE_TYPE,
+       PROP_COMPRESSION_TYPE,
+       PROP_FLAGS
+};
+
+struct _GtkSourceFileSaverPrivate
+{
+       /* Weak ref to the GtkSourceBuffer. A strong ref could create a
+        * reference cycle in an application. For example a subclass of
+        * GtkSourceBuffer can have a strong ref to the FileSaver.
+        */
+       GtkSourceBuffer *source_buffer;
+
+       /* Weak ref to the GtkSourceFile. A strong ref could create a reference
+        * cycle in an application. For example a subclass of GtkSourceFile can
+        * have a strong ref to the FileSaver.
+        */
+       GtkSourceFile *file;
+
+       GFile *location;
+
+       const GtkSourceEncoding *encoding;
+       GtkSourceNewlineType newline_type;
+       GtkSourceCompressionType compression_type;
+       GtkSourceFileSaverFlags flags;
+
+       GTask *task;
+
+       /* This field is used when cancelling the output stream: an error occurs
+        * and is stored in this field, the output stream is cancelled
+        * asynchronously, and then the error is reported to the task.
+        */
+       GError *error;
+
+       goffset total_size;
+       GFileProgressCallback progress_cb;
+       gpointer progress_cb_data;
+       GDestroyNotify progress_cb_notify;
+
+       gchar chunk_buffer[WRITE_CHUNK_SIZE];
+       gssize chunk_bytes_read;
+       gssize chunk_bytes_written;
+
+       /* The output_stream contains the required converter(s) for the encoding
+        * and the compression type.
+        * The two streams cannot be spliced directly, because
+        * (1) we need to call the progress callback
+        * (2) sync methods must be used for the input stream, and async
+        *     methods for the output stream.
+        */
+       GOutputStream *output_stream;
+       GtkSourceBufferInputStream *input_stream;
+
+       GFileInfo *info;
+
+       guint tried_mount : 1;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GtkSourceFileSaver, gtk_source_file_saver, G_TYPE_OBJECT)
+
+static void read_file_chunk (GtkSourceFileSaver *saver);
+static void write_file_chunk (GtkSourceFileSaver *saver);
+static void recover_not_mounted (GtkSourceFileSaver *saver);
+
+static void
+gtk_source_file_saver_set_property (GObject      *object,
+                                   guint         prop_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
+{
+       GtkSourceFileSaver *saver = GTK_SOURCE_FILE_SAVER (object);
+
+       switch (prop_id)
+       {
+               case PROP_BUFFER:
+                       g_assert (saver->priv->source_buffer == NULL);
+                       saver->priv->source_buffer = g_value_get_object (value);
+                       g_object_add_weak_pointer (G_OBJECT (saver->priv->source_buffer),
+                                                  (gpointer *)&saver->priv->source_buffer);
+                       break;
+
+               case PROP_FILE:
+                       g_assert (saver->priv->file == NULL);
+                       saver->priv->file = g_value_get_object (value);
+                       g_object_add_weak_pointer (G_OBJECT (saver->priv->file),
+                                                  (gpointer *)&saver->priv->file);
+                       break;
+
+               case PROP_LOCATION:
+                       g_assert (saver->priv->location == NULL);
+                       saver->priv->location = g_value_dup_object (value);
+                       break;
+
+               case PROP_ENCODING:
+                       gtk_source_file_saver_set_encoding (saver, g_value_get_boxed (value));
+                       break;
+
+               case PROP_NEWLINE_TYPE:
+                       gtk_source_file_saver_set_newline_type (saver, g_value_get_enum (value));
+                       break;
+
+               case PROP_COMPRESSION_TYPE:
+                       gtk_source_file_saver_set_compression_type (saver, g_value_get_enum (value));
+                       break;
+
+               case PROP_FLAGS:
+                       gtk_source_file_saver_set_flags (saver, g_value_get_flags (value));
+                       break;
+
+               default:
+                       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                       break;
+       }
+}
+
+static void
+gtk_source_file_saver_get_property (GObject    *object,
+                                   guint       prop_id,
+                                   GValue     *value,
+                                   GParamSpec *pspec)
+{
+       GtkSourceFileSaver *saver = GTK_SOURCE_FILE_SAVER (object);
+
+       switch (prop_id)
+       {
+               case PROP_BUFFER:
+                       g_value_set_object (value, saver->priv->source_buffer);
+                       break;
+
+               case PROP_FILE:
+                       g_value_set_object (value, saver->priv->file);
+                       break;
+
+               case PROP_LOCATION:
+                       g_value_set_object (value, saver->priv->location);
+                       break;
+
+               case PROP_ENCODING:
+                       g_value_set_boxed (value, saver->priv->encoding);
+                       break;
+
+               case PROP_NEWLINE_TYPE:
+                       g_value_set_enum (value, saver->priv->newline_type);
+                       break;
+
+               case PROP_COMPRESSION_TYPE:
+                       g_value_set_enum (value, saver->priv->compression_type);
+                       break;
+
+               case PROP_FLAGS:
+                       g_value_set_flags (value, saver->priv->flags);
+                       break;
+
+               default:
+                       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                       break;
+       }
+}
+
+static void
+reset (GtkSourceFileSaver *saver)
+{
+       g_clear_object (&saver->priv->task);
+       g_clear_object (&saver->priv->output_stream);
+       g_clear_object (&saver->priv->input_stream);
+       g_clear_object (&saver->priv->info);
+       g_clear_error (&saver->priv->error);
+
+       if (saver->priv->progress_cb_notify != NULL)
+       {
+               saver->priv->progress_cb_notify (saver->priv->progress_cb_data);
+               saver->priv->progress_cb_notify = NULL;
+       }
+
+       saver->priv->progress_cb = NULL;
+       saver->priv->progress_cb_data = NULL;
+}
+
+static void
+gtk_source_file_saver_dispose (GObject *object)
+{
+       GtkSourceFileSaver *saver = GTK_SOURCE_FILE_SAVER (object);
+
+       reset (saver);
+
+       if (saver->priv->source_buffer != NULL)
+       {
+               g_object_remove_weak_pointer (G_OBJECT (saver->priv->source_buffer),
+                                             (gpointer *)&saver->priv->source_buffer);
+
+               saver->priv->source_buffer = NULL;
+       }
+
+       if (saver->priv->file != NULL)
+       {
+               g_object_remove_weak_pointer (G_OBJECT (saver->priv->file),
+                                             (gpointer *)&saver->priv->file);
+
+               saver->priv->file = NULL;
+       }
+
+       g_clear_object (&saver->priv->location);
+
+       G_OBJECT_CLASS (gtk_source_file_saver_parent_class)->dispose (object);
+}
+
+static void
+gtk_source_file_saver_constructed (GObject *object)
+{
+       GtkSourceFileSaver *saver = GTK_SOURCE_FILE_SAVER (object);
+
+       if (saver->priv->file != NULL)
+       {
+               const GtkSourceEncoding *encoding;
+               GtkSourceNewlineType newline_type;
+               GtkSourceCompressionType compression_type;
+
+               encoding = gtk_source_file_get_encoding (saver->priv->file);
+               gtk_source_file_saver_set_encoding (saver, encoding);
+
+               newline_type = gtk_source_file_get_newline_type (saver->priv->file);
+               gtk_source_file_saver_set_newline_type (saver, newline_type);
+
+               compression_type = gtk_source_file_get_compression_type (saver->priv->file);
+               gtk_source_file_saver_set_compression_type (saver, compression_type);
+       }
+
+       G_OBJECT_CLASS (gtk_source_file_saver_parent_class)->constructed (object);
+}
+
+static void
+gtk_source_file_saver_class_init (GtkSourceFileSaverClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->dispose = gtk_source_file_saver_dispose;
+       object_class->set_property = gtk_source_file_saver_set_property;
+       object_class->get_property = gtk_source_file_saver_get_property;
+       object_class->constructed = gtk_source_file_saver_constructed;
+
+       /**
+        * GtkSourceFileSaver:buffer:
+        *
+        * The #GtkSourceBuffer to save. The #GtkSourceFileSaver object has a
+        * weak reference to the :buffer.
+        *
+        * Since: 3.14
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_BUFFER,
+                                        g_param_spec_object ("buffer",
+                                                             "GtkSourceBuffer",
+                                                             "",
+                                                             GTK_SOURCE_TYPE_BUFFER,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY |
+                                                             G_PARAM_STATIC_STRINGS));
+
+       /**
+        * GtkSourceFileSaver:file:
+        *
+        * The #GtkSourceFile. The #GtkSourceFileSaver object has a weak
+        * reference to the :file.
+        *
+        * Since: 3.14
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_FILE,
+                                        g_param_spec_object ("file",
+                                                             "GtkSourceFile",
+                                                             "",
+                                                             GTK_SOURCE_TYPE_FILE,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY |
+                                                             G_PARAM_STATIC_STRINGS));
+
+       /**
+        * GtkSourceFileSaver:location:
+        *
+        * The #GFile where to save the buffer.
+        *
+        * Since: 3.14
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_LOCATION,
+                                        g_param_spec_object ("location",
+                                                             _("Location"),
+                                                             "",
+                                                             G_TYPE_FILE,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY |
+                                                             G_PARAM_STATIC_STRINGS));
+
+       /**
+        * GtkSourceFileSaver:encoding:
+        *
+        * The file's encoding.
+        *
+        * Since: 3.14
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_ENCODING,
+                                        g_param_spec_boxed ("encoding",
+                                                            _("Encoding"),
+                                                            "",
+                                                            GTK_SOURCE_TYPE_ENCODING,
+                                                            G_PARAM_READWRITE |
+                                                            G_PARAM_CONSTRUCT |
+                                                            G_PARAM_STATIC_STRINGS));
+
+       /**
+        * GtkSourceFileSaver:newline-type:
+        *
+        * The newline type.
+        *
+        * Since: 3.14
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_NEWLINE_TYPE,
+                                        g_param_spec_enum ("newline-type",
+                                                           _("Newline type"),
+                                                           "",
+                                                           GTK_SOURCE_TYPE_NEWLINE_TYPE,
+                                                           GTK_SOURCE_NEWLINE_TYPE_LF,
+                                                           G_PARAM_READWRITE |
+                                                           G_PARAM_CONSTRUCT |
+                                                           G_PARAM_STATIC_STRINGS));
+
+       /**
+        * GtkSourceFileSaver:compression-type:
+        *
+        * The compression type.
+        *
+        * Since: 3.14
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_COMPRESSION_TYPE,
+                                        g_param_spec_enum ("compression-type",
+                                                           _("Compression type"),
+                                                           "",
+                                                           GTK_SOURCE_TYPE_COMPRESSION_TYPE,
+                                                           GTK_SOURCE_COMPRESSION_TYPE_NONE,
+                                                           G_PARAM_READWRITE |
+                                                           G_PARAM_CONSTRUCT |
+                                                           G_PARAM_STATIC_STRINGS));
+
+       /**
+        * GtkSourceFileSaver:flags:
+        *
+        * File saving flags.
+        *
+        * Since: 3.14
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_FLAGS,
+                                        g_param_spec_flags ("flags",
+                                                            _("Flags"),
+                                                            "",
+                                                            GTK_SOURCE_TYPE_FILE_SAVER_FLAGS,
+                                                            GTK_SOURCE_FILE_SAVER_FLAGS_NONE,
+                                                            G_PARAM_READWRITE |
+                                                            G_PARAM_CONSTRUCT |
+                                                            G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gtk_source_file_saver_init (GtkSourceFileSaver *saver)
+{
+       saver->priv = gtk_source_file_saver_get_instance_private (saver);
+}
+
+/* BEGIN NOTE:
+ *
+ * This fixes an issue in GOutputStream that applies the atomic replace save
+ * strategy. The stream moves the written file to the original file when the
+ * stream is closed. However, there is no way currently to tell the stream that
+ * the save should be aborted (there could be a conversion error). The patch
+ * explicitly closes the output stream in all these cases with a GCancellable in
+ * the cancelled state, causing the output stream to close, but not move the
+ * file. This makes use of an implementation detail in the local file stream
+ * and should be properly fixed by adding the appropriate API in GIO. Until
+ * then, at least we prevent data corruption for now.
+ *
+ * Relevant bug reports:
+ *
+ * Bug 615110 - write file ignore encoding errors (gedit)
+ * https://bugzilla.gnome.org/show_bug.cgi?id=615110
+ *
+ * Bug 602412 - g_file_replace does not restore original file when there is
+ *              errors while writing (glib/gio)
+ * https://bugzilla.gnome.org/show_bug.cgi?id=602412
+ */
+static void
+cancel_output_stream_ready_cb (GOutputStream      *output_stream,
+                              GAsyncResult       *result,
+                              GtkSourceFileSaver *saver)
+{
+       g_output_stream_close_finish (output_stream, result, NULL);
+
+       if (saver->priv->error != NULL)
+       {
+               GError *error = saver->priv->error;
+               saver->priv->error = NULL;
+               g_task_return_error (saver->priv->task, error);
+       }
+       else
+       {
+               g_task_return_boolean (saver->priv->task, FALSE);
+       }
+}
+
+static void
+cancel_output_stream (GtkSourceFileSaver *saver)
+{
+       GCancellable *cancellable;
+
+       DEBUG ({
+              g_print ("Cancel output stream\n");
+       });
+
+       cancellable = g_cancellable_new ();
+       g_cancellable_cancel (cancellable);
+
+       g_output_stream_close_async (saver->priv->output_stream,
+                                    g_task_get_priority (saver->priv->task),
+                                    cancellable,
+                                    (GAsyncReadyCallback) cancel_output_stream_ready_cb,
+                                    saver);
+
+       g_object_unref (cancellable);
+}
+
+/*
+ * END NOTE
+ */
+
+static void
+query_info_cb (GFile              *file,
+              GAsyncResult       *result,
+              GtkSourceFileSaver *saver)
+{
+       GError *error = NULL;
+
+       DEBUG ({
+              g_print ("Finished query info on file\n");
+       });
+
+       g_clear_object (&saver->priv->info);
+       saver->priv->info = g_file_query_info_finish (file, result, &error);
+
+       if (error != NULL)
+       {
+               DEBUG ({
+                      g_print ("Query info failed: %s\n", error->message);
+               });
+
+               g_task_return_error (saver->priv->task, error);
+               return;
+       }
+
+       g_task_return_boolean (saver->priv->task, TRUE);
+}
+
+static void
+close_output_stream_cb (GOutputStream      *output_stream,
+                       GAsyncResult       *result,
+                       GtkSourceFileSaver *saver)
+{
+       GError *error = NULL;
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       g_output_stream_close_finish (output_stream, result, &error);
+
+       if (error != NULL)
+       {
+               DEBUG ({
+                      g_print ("Closing stream error: %s\n", error->message);
+               });
+
+               g_task_return_error (saver->priv->task, error);
+               return;
+       }
+
+       /* Get the file info: note we cannot use
+        * g_file_output_stream_query_info_async() since it is not able to get
+        * the modification time.
+        */
+       DEBUG ({
+              g_print ("Query info on file\n");
+       });
+
+       g_file_query_info_async (saver->priv->location,
+                                QUERY_ATTRIBUTES,
+                                G_FILE_QUERY_INFO_NONE,
+                                g_task_get_priority (saver->priv->task),
+                                g_task_get_cancellable (saver->priv->task),
+                                (GAsyncReadyCallback) query_info_cb,
+                                saver);
+}
+
+static void
+write_complete (GtkSourceFileSaver *saver)
+{
+       GError *error = NULL;
+
+       DEBUG ({
+              g_print ("Close input stream\n");
+       });
+
+       g_input_stream_close (G_INPUT_STREAM (saver->priv->input_stream),
+                             g_task_get_cancellable (saver->priv->task),
+                             &error);
+
+       if (error != NULL)
+       {
+               DEBUG ({
+                      g_print ("Closing input stream error: %s\n", error->message);
+               });
+
+               saver->priv->error = error;
+               cancel_output_stream (saver);
+               return;
+       }
+
+       DEBUG ({
+              g_print ("Close output stream\n");
+       });
+
+       g_output_stream_close_async (saver->priv->output_stream,
+                                    g_task_get_priority (saver->priv->task),
+                                    g_task_get_cancellable (saver->priv->task),
+                                    (GAsyncReadyCallback) close_output_stream_cb,
+                                    saver);
+}
+
+static void
+write_file_chunk_cb (GOutputStream      *output_stream,
+                    GAsyncResult       *result,
+                    GtkSourceFileSaver *saver)
+{
+       gssize bytes_written;
+       GError *error = NULL;
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       bytes_written = g_output_stream_write_finish (output_stream, result, &error);
+
+       DEBUG ({
+              g_print ("Written: %" G_GSSIZE_FORMAT "\n", bytes_written);
+       });
+
+       if (error != NULL)
+       {
+               DEBUG ({
+                      g_print ("Write error: %s\n", error->message);
+               });
+
+               saver->priv->error = error;
+               cancel_output_stream (saver);
+               return;
+       }
+
+       saver->priv->chunk_bytes_written += bytes_written;
+
+       /* Write again */
+       if (saver->priv->chunk_bytes_read != saver->priv->chunk_bytes_written)
+       {
+               write_file_chunk (saver);
+               return;
+       }
+
+       if (saver->priv->progress_cb != NULL)
+       {
+               gsize total_chars_written;
+
+               total_chars_written = _gtk_source_buffer_input_stream_tell (saver->priv->input_stream);
+
+               saver->priv->progress_cb (total_chars_written,
+                                         saver->priv->total_size,
+                                         saver->priv->progress_cb_data);
+       }
+
+       read_file_chunk (saver);
+}
+
+static void
+write_file_chunk (GtkSourceFileSaver *saver)
+{
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       /* FIXME check if a thread is created each time this function is called.
+        * If so, this is a performance problem and should be fixed.
+        */
+       g_output_stream_write_async (saver->priv->output_stream,
+                                    saver->priv->chunk_buffer + saver->priv->chunk_bytes_written,
+                                    saver->priv->chunk_bytes_read - saver->priv->chunk_bytes_written,
+                                    g_task_get_priority (saver->priv->task),
+                                    g_task_get_cancellable (saver->priv->task),
+                                    (GAsyncReadyCallback) write_file_chunk_cb,
+                                    saver);
+}
+
+static void
+read_file_chunk (GtkSourceFileSaver *saver)
+{
+       GError *error = NULL;
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       saver->priv->chunk_bytes_written = 0;
+
+       /* We use sync methods on doc stream since it is in memory. Using async
+        * would be racy and we can end up with invalid iters.
+        */
+       saver->priv->chunk_bytes_read = g_input_stream_read (G_INPUT_STREAM (saver->priv->input_stream),
+                                                            saver->priv->chunk_buffer,
+                                                            WRITE_CHUNK_SIZE,
+                                                            g_task_get_cancellable (saver->priv->task),
+                                                            &error);
+
+       if (error != NULL)
+       {
+               saver->priv->error = error;
+               cancel_output_stream (saver);
+               return;
+       }
+
+       /* Check if we finished reading and writing. */
+       if (saver->priv->chunk_bytes_read == 0)
+       {
+               write_complete (saver);
+               return;
+       }
+
+       write_file_chunk (saver);
+}
+
+static void
+replace_file_cb (GFile              *file,
+                GAsyncResult       *result,
+                GtkSourceFileSaver *saver)
+{
+       GFileOutputStream *file_output_stream;
+       GOutputStream *output_stream;
+       GError *error = NULL;
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       file_output_stream = g_file_replace_finish (file, result, &error);
+
+       if (error != NULL)
+       {
+               if (error->domain == G_IO_ERROR &&
+                   error->code == G_IO_ERROR_NOT_MOUNTED &&
+                   !saver->priv->tried_mount)
+               {
+                       recover_not_mounted (saver);
+                       g_error_free (error);
+                       return;
+               }
+
+               DEBUG ({
+                      g_print ("Opening file failed: %s\n", error->message);
+               });
+
+               g_task_return_error (saver->priv->task, error);
+               return;
+       }
+
+       if (saver->priv->compression_type == GTK_SOURCE_COMPRESSION_TYPE_GZIP)
+       {
+               GZlibCompressor *compressor;
+
+               DEBUG ({
+                      g_print ("Use gzip compressor\n");
+               });
+
+               compressor = g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP, -1);
+
+               output_stream = g_converter_output_stream_new (G_OUTPUT_STREAM (file_output_stream),
+                                                              G_CONVERTER (compressor));
+
+               g_object_unref (compressor);
+               g_object_unref (file_output_stream);
+       }
+       else
+       {
+               output_stream = G_OUTPUT_STREAM (file_output_stream);
+       }
+
+       /* FIXME: manage converter error? */
+
+       DEBUG ({
+              g_print ("Encoding charset: %s\n",
+                       gtk_source_encoding_get_charset (saver->priv->encoding));
+       });
+
+       if (saver->priv->encoding != gtk_source_encoding_get_utf8 ())
+       {
+               GCharsetConverter *converter;
+
+               converter = g_charset_converter_new (gtk_source_encoding_get_charset (saver->priv->encoding),
+                                                    "UTF-8",
+                                                    NULL);
+
+               saver->priv->output_stream = g_converter_output_stream_new (output_stream,
+                                                                           G_CONVERTER (converter));
+
+               g_object_unref (converter);
+               g_object_unref (output_stream);
+       }
+       else
+       {
+               saver->priv->output_stream = G_OUTPUT_STREAM (output_stream);
+       }
+
+       saver->priv->total_size = _gtk_source_buffer_input_stream_get_total_size (saver->priv->input_stream);
+
+       DEBUG ({
+              g_print ("Total number of characters: %" G_GINT64_FORMAT "\n", saver->priv->total_size);
+       });
+
+       read_file_chunk (saver);
+}
+
+static void
+begin_write (GtkSourceFileSaver *saver)
+{
+       gboolean create_backup = (saver->priv->flags & GTK_SOURCE_FILE_SAVER_FLAGS_CREATE_BACKUP) != 0;
+
+       DEBUG ({
+              g_print ("Start replacing file contents\n");
+              g_print ("Make backup: %s\n", create_backup ? "yes" : "no");
+       });
+
+       g_file_replace_async (saver->priv->location,
+                             NULL,
+                             create_backup,
+                             G_FILE_CREATE_NONE,
+                             g_task_get_priority (saver->priv->task),
+                             g_task_get_cancellable (saver->priv->task),
+                             (GAsyncReadyCallback) replace_file_cb,
+                             saver);
+}
+
+static void
+check_externally_modified_cb (GFile              *location,
+                             GAsyncResult       *result,
+                             GtkSourceFileSaver *saver)
+{
+       GFileInfo *info;
+       GTimeVal old_mtime;
+       GTimeVal cur_mtime;
+       GError *error = NULL;
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       info = g_file_query_info_finish (location, result, &error);
+
+       if (error != NULL)
+       {
+               if (error->domain == G_IO_ERROR &&
+                   error->code == G_IO_ERROR_NOT_MOUNTED &&
+                   !saver->priv->tried_mount)
+               {
+                       recover_not_mounted (saver);
+                       g_error_free (error);
+                       return;
+               }
+
+               /* It's perfectly fine if the file doesn't exist yet. */
+               if (error->domain != G_IO_ERROR ||
+                   error->code != G_IO_ERROR_NOT_FOUND)
+               {
+                       DEBUG ({
+                              g_print ("Check externally modified failed: %s\n", error->message);
+                       });
+
+                       g_task_return_error (saver->priv->task, error);
+                       return;
+               }
+               else
+               {
+                       g_error_free (error);
+               }
+       }
+
+       /* FIXME for a "save as" it doesn't work, the mtime stored in
+        * GtkSourceFile is not for the good file.
+        */
+       if (_gtk_source_file_get_modification_time (saver->priv->file, &old_mtime) &&
+           info != NULL &&
+           g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED))
+       {
+               g_file_info_get_modification_time (info, &cur_mtime);
+
+               if (old_mtime.tv_sec != cur_mtime.tv_sec ||
+                   old_mtime.tv_usec != cur_mtime.tv_usec)
+               {
+                       DEBUG ({
+                              g_print ("The file is externally modified\n");
+                       });
+
+                       g_task_return_new_error (saver->priv->task,
+                                                GTK_SOURCE_FILE_SAVER_ERROR,
+                                                GTK_SOURCE_FILE_SAVER_ERROR_EXTERNALLY_MODIFIED,
+                                                "The file is externally modified");
+                       g_object_unref (info);
+                       return;
+               }
+       }
+
+       begin_write (saver);
+
+       if (info != NULL)
+       {
+               g_object_unref (info);
+       }
+}
+
+static void
+check_externally_modified (GtkSourceFileSaver *saver)
+{
+       if (saver->priv->flags & GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_MODIFICATION_TIME)
+       {
+               begin_write (saver);
+               return;
+       }
+
+       DEBUG ({
+              g_print ("Check externally modified\n");
+       });
+
+       g_file_query_info_async (saver->priv->location,
+                                G_FILE_ATTRIBUTE_TIME_MODIFIED,
+                                G_FILE_QUERY_INFO_NONE,
+                                g_task_get_priority (saver->priv->task),
+                                g_task_get_cancellable (saver->priv->task),
+                                (GAsyncReadyCallback) check_externally_modified_cb,
+                                saver);
+}
+
+static void
+mount_cb (GFile              *file,
+         GAsyncResult       *result,
+         GtkSourceFileSaver *saver)
+{
+       GError *error = NULL;
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       g_file_mount_enclosing_volume_finish (file, result, &error);
+
+       if (error != NULL)
+       {
+               g_task_return_error (saver->priv->task, error);
+       }
+
+       check_externally_modified (saver);
+}
+
+static void
+recover_not_mounted (GtkSourceFileSaver *saver)
+{
+       GMountOperation *mount_operation;
+
+       mount_operation = _gtk_source_file_create_mount_operation (saver->priv->file);
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       saver->priv->tried_mount = TRUE;
+
+       g_file_mount_enclosing_volume (saver->priv->location,
+                                      G_MOUNT_MOUNT_NONE,
+                                      mount_operation,
+                                      g_task_get_cancellable (saver->priv->task),
+                                      (GAsyncReadyCallback) mount_cb,
+                                      saver);
+
+       g_object_unref (mount_operation);
+}
+
+GQuark
+gtk_source_file_saver_error_quark (void)
+{
+       static GQuark quark = 0;
+
+       if (G_UNLIKELY (quark == 0))
+       {
+               quark = g_quark_from_static_string ("gtk-source-file-saver-error");
+       }
+
+       return quark;
+}
+
+/**
+ * gtk_source_file_saver_new:
+ * @buffer: the #GtkSourceBuffer to save.
+ * @file: the #GtkSourceFile.
+ * @location: the #GFile where to save the buffer to.
+ *
+ * Returns: a new #GtkSourceFileSaver object.
+ * Since: 3.14
+ */
+GtkSourceFileSaver *
+gtk_source_file_saver_new (GtkSourceBuffer *buffer,
+                          GtkSourceFile   *file,
+                          GFile           *location)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL);
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), NULL);
+       g_return_val_if_fail (G_IS_FILE (location), NULL);
+
+       return g_object_new (GTK_SOURCE_TYPE_FILE_SAVER,
+                            "buffer", buffer,
+                            "file", file,
+                            "location", location,
+                            NULL);
+}
+
+/**
+ * gtk_source_file_saver_get_buffer:
+ * @saver: a #GtkSourceFileSaver.
+ *
+ * Returns: (transfer none): the #GtkSourceBuffer to save.
+ * Since: 3.14
+ */
+GtkSourceBuffer *
+gtk_source_file_saver_get_buffer (GtkSourceFileSaver *saver)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver), NULL);
+
+       return saver->priv->source_buffer;
+}
+
+/**
+ * gtk_source_file_saver_get_file:
+ * @saver: a #GtkSourceFileSaver.
+ *
+ * Returns: (transfer none): the #GtkSourceFile.
+ * Since: 3.14
+ */
+GtkSourceFile *
+gtk_source_file_saver_get_file (GtkSourceFileSaver *saver)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver), NULL);
+
+       return saver->priv->file;
+}
+
+/**
+ * gtk_source_file_saver_get_location:
+ * @saver: a #GtkSourceFileSaver.
+ *
+ * Returns: (transfer none): the #GFile where to save the buffer to.
+ * Since: 3.14
+ */
+GFile *
+gtk_source_file_saver_get_location (GtkSourceFileSaver *saver)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver), NULL);
+
+       return saver->priv->location;
+}
+
+/**
+ * gtk_source_file_saver_set_encoding:
+ * @saver: a #GtkSourceFileSaver.
+ * @encoding: (allow-none): the new encoding, or %NULL for UTF-8.
+ *
+ * Changes the #GtkSourceFileSaver:file encoding that will be used for the next
+ * file saving. If @encoding is %NULL, the UTF-8 encoding will be set.
+ *
+ * Note that a #GtkSourceBuffer has always a UTF-8 encoding.
+ *
+ * Since: 3.14
+ */
+void
+gtk_source_file_saver_set_encoding (GtkSourceFileSaver      *saver,
+                                   const GtkSourceEncoding *encoding)
+{
+       g_return_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver));
+       g_return_if_fail (saver->priv->task == NULL);
+
+       if (encoding == NULL)
+       {
+               encoding = gtk_source_encoding_get_utf8 ();
+       }
+
+       if (saver->priv->encoding != encoding)
+       {
+               saver->priv->encoding = encoding;
+               g_object_notify (G_OBJECT (saver), "encoding");
+       }
+}
+
+/**
+ * gtk_source_file_saver_get_encoding:
+ * @saver: a #GtkSourceFileSaver.
+ *
+ * Returns: the encoding.
+ * Since: 3.14
+ */
+const GtkSourceEncoding *
+gtk_source_file_saver_get_encoding (GtkSourceFileSaver *saver)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver), NULL);
+
+       return saver->priv->encoding;
+}
+
+/**
+ * gtk_source_file_saver_set_newline_type:
+ * @saver: a #GtkSourceFileSaver.
+ * @newline_type: the new newline type.
+ *
+ * Changes the newline type that will be used for the next file saving.
+ *
+ * Since: 3.14
+ */
+void
+gtk_source_file_saver_set_newline_type (GtkSourceFileSaver   *saver,
+                                       GtkSourceNewlineType  newline_type)
+{
+       g_return_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver));
+       g_return_if_fail (saver->priv->task == NULL);
+
+       if (saver->priv->newline_type != newline_type)
+       {
+               saver->priv->newline_type = newline_type;
+               g_object_notify (G_OBJECT (saver), "newline-type");
+       }
+}
+
+/**
+ * gtk_source_file_saver_get_newline_type:
+ * @saver: a #GtkSourceFileSaver.
+ *
+ * Returns: the newline type.
+ * Since: 3.14
+ */
+GtkSourceNewlineType
+gtk_source_file_saver_get_newline_type (GtkSourceFileSaver *saver)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver), GTK_SOURCE_NEWLINE_TYPE_DEFAULT);
+
+       return saver->priv->newline_type;
+}
+
+/**
+ * gtk_source_file_saver_set_compression_type:
+ * @saver: a #GtkSourceFileSaver.
+ * @compression_type: the new compression type.
+ *
+ * Changes the #GtkSourceFileSaver:file compression type that will be used for
+ * the next file saving.
+ *
+ * Since: 3.14
+ */
+void
+gtk_source_file_saver_set_compression_type (GtkSourceFileSaver       *saver,
+                                           GtkSourceCompressionType  compression_type)
+{
+       g_return_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver));
+       g_return_if_fail (saver->priv->task == NULL);
+
+       if (saver->priv->compression_type != compression_type)
+       {
+               saver->priv->compression_type = compression_type;
+               g_object_notify (G_OBJECT (saver), "compression-type");
+       }
+}
+
+/**
+ * gtk_source_file_saver_get_compression_type:
+ * @saver: a #GtkSourceFileSaver.
+ *
+ * Returns: the compression type.
+ * Since: 3.14
+ */
+GtkSourceCompressionType
+gtk_source_file_saver_get_compression_type (GtkSourceFileSaver *saver)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver), GTK_SOURCE_COMPRESSION_TYPE_NONE);
+
+       return saver->priv->compression_type;
+}
+
+/**
+ * gtk_source_file_saver_set_flags:
+ * @saver: a #GtkSourceFileSaver.
+ * @flags: the new flags.
+ *
+ * Since: 3.14
+ */
+void
+gtk_source_file_saver_set_flags (GtkSourceFileSaver      *saver,
+                                GtkSourceFileSaverFlags  flags)
+{
+       g_return_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver));
+       g_return_if_fail (saver->priv->task == NULL);
+
+       if (saver->priv->flags != flags)
+       {
+               saver->priv->flags = flags;
+               g_object_notify (G_OBJECT (saver), "flags");
+       }
+}
+
+/**
+ * gtk_source_file_saver_get_flags:
+ * @saver: a #GtkSourceFileSaver.
+ *
+ * Returns: the flags.
+ * Since: 3.14
+ */
+GtkSourceFileSaverFlags
+gtk_source_file_saver_get_flags (GtkSourceFileSaver *saver)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver), GTK_SOURCE_FILE_SAVER_FLAGS_NONE);
+
+       return saver->priv->flags;
+}
+
+/**
+ * gtk_source_file_saver_save_async:
+ * @saver: a #GtkSourceFileSaver.
+ * @io_priority: the I/O priority of the request. E.g. %G_PRIORITY_LOW,
+ *   %G_PRIORITY_DEFAULT or %G_PRIORITY_HIGH.
+ * @cancellable: (nullable): optional #GCancellable object, %NULL to ignore.
+ * @progress_callback: (scope notified) (nullable): function to call back with
+ *   progress information, or %NULL if progress information is not needed.
+ * @progress_callback_data: (closure): user data to pass to @progress_callback.
+ * @progress_callback_notify: (nullable): function to call on
+ *   @progress_callback_data when the @progress_callback is no longer needed, or
+ *   %NULL.
+ * @callback: (scope async): a #GAsyncReadyCallback to call when the request is
+ *   satisfied.
+ * @user_data: user data to pass to @callback.
+ *
+ * Saves asynchronously the buffer into the file. See the #GAsyncResult
+ * documentation to know how to use this function.
+ *
+ * Since: 3.14
+ */
+
+/* The GDestroyNotify is needed, currently the following bug is not fixed:
+ * https://bugzilla.gnome.org/show_bug.cgi?id=616044
+ */
+void
+gtk_source_file_saver_save_async (GtkSourceFileSaver     *saver,
+                                 gint                    io_priority,
+                                 GCancellable           *cancellable,
+                                 GFileProgressCallback   progress_callback,
+                                 gpointer                progress_callback_data,
+                                 GDestroyNotify          progress_callback_notify,
+                                 GAsyncReadyCallback     callback,
+                                 gpointer                user_data)
+{
+       gboolean check_invalid_chars;
+       gboolean implicit_trailing_newline;
+
+       g_return_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver));
+       g_return_if_fail (saver->priv->task == NULL);
+
+       if (saver->priv->source_buffer == NULL ||
+           saver->priv->file == NULL)
+       {
+               return;
+       }
+
+       reset (saver);
+
+       saver->priv->task = g_task_new (saver, cancellable, callback, user_data);
+       g_task_set_priority (saver->priv->task, io_priority);
+
+       saver->priv->progress_cb = progress_callback;
+       saver->priv->progress_cb_data = progress_callback_data;
+       saver->priv->progress_cb_notify = progress_callback_notify;
+
+       check_invalid_chars = (saver->priv->flags & GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_INVALID_CHARS) == 0;
+
+       if (check_invalid_chars && _gtk_source_buffer_has_invalid_chars (saver->priv->source_buffer))
+       {
+               g_task_return_new_error (saver->priv->task,
+                                        GTK_SOURCE_FILE_SAVER_ERROR,
+                                        GTK_SOURCE_FILE_SAVER_ERROR_INVALID_CHARS,
+                                        "The buffer contains invalid characters");
+               return;
+       }
+
+       DEBUG ({
+              g_print ("Start saving\n");
+       });
+
+       implicit_trailing_newline = gtk_source_buffer_get_implicit_trailing_newline 
(saver->priv->source_buffer);
+
+       /* The BufferInputStream has a strong reference to the buffer.
+        * We create the BufferInputStream here so we are sure that the
+        * buffer will not be destroyed during the file saving.
+        */
+       saver->priv->input_stream = _gtk_source_buffer_input_stream_new (GTK_TEXT_BUFFER 
(saver->priv->source_buffer),
+                                                                        saver->priv->newline_type,
+                                                                        implicit_trailing_newline);
+
+       check_externally_modified (saver);
+}
+
+/**
+ * gtk_source_file_saver_save_finish:
+ * @saver: a #GtkSourceFileSaver.
+ * @result: a #GAsyncResult.
+ * @error: a #GError, or %NULL.
+ *
+ * Finishes a file saving started with gtk_source_file_saver_save_async().
+ *
+ * If the file has been saved successfully, the following #GtkSourceFile
+ * properties will be updated: the location, the encoding, the newline type and
+ * the compression type.
+ *
+ * Returns: whether the file was saved successfully.
+ * Since: 3.14
+ */
+gboolean
+gtk_source_file_saver_save_finish (GtkSourceFileSaver  *saver,
+                                  GAsyncResult        *result,
+                                  GError             **error)
+{
+       gboolean ok;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver), FALSE);
+       g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+       g_return_val_if_fail (g_task_is_valid (result, saver), FALSE);
+
+       ok = g_task_propagate_boolean (G_TASK (result), error);
+
+       if (ok && saver->priv->file != NULL)
+       {
+               gtk_source_file_set_location (saver->priv->file,
+                                             saver->priv->location);
+
+               _gtk_source_file_set_encoding (saver->priv->file,
+                                              saver->priv->encoding);
+
+               _gtk_source_file_set_newline_type (saver->priv->file,
+                                                  saver->priv->newline_type);
+
+               _gtk_source_file_set_compression_type (saver->priv->file,
+                                                      saver->priv->compression_type);
+
+               if (g_file_info_has_attribute (saver->priv->info, G_FILE_ATTRIBUTE_TIME_MODIFIED))
+               {
+                       GTimeVal modification_time;
+                       g_file_info_get_modification_time (saver->priv->info, &modification_time);
+                       _gtk_source_file_set_modification_time (saver->priv->file, modification_time);
+               }
+       }
+
+       reset (saver);
+
+       return ok;
+}
diff --git a/gtksourceview/gtksourcefilesaver.h b/gtksourceview/gtksourcefilesaver.h
new file mode 100644
index 0000000..6040064
--- /dev/null
+++ b/gtksourceview/gtksourcefilesaver.h
@@ -0,0 +1,144 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* gtksourcefilesaver.h
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2005, 2007 - Paolo Maggi
+ * Copyrhing (C) 2007 - Steve Frécinaux
+ * Copyright (C) 2008 - Jesse van den Kieboom
+ * Copyright (C) 2014 - Sébastien Wilmet
+ *
+ * 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_FILE_SAVER_H__
+#define __GTK_SOURCE_FILE_SAVER_H__
+
+#include <gtk/gtk.h>
+#include <gtksourceview/gtksourcetypes.h>
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_FILE_SAVER              (gtk_source_file_saver_get_type())
+#define GTK_SOURCE_FILE_SAVER(obj)              (G_TYPE_CHECK_INSTANCE_CAST((obj), 
GTK_SOURCE_TYPE_FILE_SAVER, GtkSourceFileSaver))
+#define GTK_SOURCE_FILE_SAVER_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST((klass), 
GTK_SOURCE_TYPE_FILE_SAVER, GtkSourceFileSaverClass))
+#define GTK_SOURCE_IS_FILE_SAVER(obj)           (G_TYPE_CHECK_INSTANCE_TYPE((obj), 
GTK_SOURCE_TYPE_FILE_SAVER))
+#define GTK_SOURCE_IS_FILE_SAVER_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), 
GTK_SOURCE_TYPE_FILE_SAVER))
+#define GTK_SOURCE_FILE_SAVER_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS((obj), 
GTK_SOURCE_TYPE_FILE_SAVER, GtkSourceFileSaverClass))
+
+typedef struct _GtkSourceFileSaverClass   GtkSourceFileSaverClass;
+typedef struct _GtkSourceFileSaverPrivate GtkSourceFileSaverPrivate;
+
+#define GTK_SOURCE_FILE_SAVER_ERROR gtk_source_file_saver_error_quark ()
+
+/**
+ * GtkSourceFileSaverError:
+ * @GTK_SOURCE_FILE_SAVER_ERROR_INVALID_CHARS: The buffer contains invalid
+ *   characters.
+ * @GTK_SOURCE_FILE_SAVER_ERROR_EXTERNALLY_MODIFIED: The file is externally
+ *   modified.
+ *
+ * An error code used with the %GTK_SOURCE_FILE_SAVER_ERROR domain.
+ * Since: 3.14
+ */
+typedef enum
+{
+       GTK_SOURCE_FILE_SAVER_ERROR_INVALID_CHARS,
+       GTK_SOURCE_FILE_SAVER_ERROR_EXTERNALLY_MODIFIED
+} GtkSourceFileSaverError;
+
+/**
+ * GtkSourceFileSaverFlags:
+ * @GTK_SOURCE_FILE_SAVER_FLAGS_NONE: No flags.
+ * @GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_INVALID_CHARS: Ignore invalid characters.
+ * @GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_MODIFICATION_TIME: Save file despite external modifications.
+ * @GTK_SOURCE_FILE_SAVER_FLAGS_CREATE_BACKUP: Create a backup before saving the file.
+ *
+ * Flags to define the behavior of a #GtkSourceFileSaver.
+ * Since: 3.14
+ */
+typedef enum
+{
+       GTK_SOURCE_FILE_SAVER_FLAGS_NONE                        = 0,
+       GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_INVALID_CHARS        = 1 << 0,
+       GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_MODIFICATION_TIME    = 1 << 1,
+       GTK_SOURCE_FILE_SAVER_FLAGS_CREATE_BACKUP               = 1 << 2
+} GtkSourceFileSaverFlags;
+
+struct _GtkSourceFileSaver
+{
+       GObject object;
+
+       GtkSourceFileSaverPrivate *priv;
+};
+
+struct _GtkSourceFileSaverClass
+{
+       GObjectClass parent_class;
+
+       gpointer padding[10];
+};
+
+GType                   gtk_source_file_saver_get_type         (void) G_GNUC_CONST;
+
+GQuark                  gtk_source_file_saver_error_quark      (void);
+
+GtkSourceFileSaver     *gtk_source_file_saver_new              (GtkSourceBuffer          *buffer,
+                                                                GtkSourceFile            *file,
+                                                                GFile                    *location);
+
+GtkSourceBuffer                *gtk_source_file_saver_get_buffer       (GtkSourceFileSaver       *saver);
+
+GtkSourceFile          *gtk_source_file_saver_get_file         (GtkSourceFileSaver       *saver);
+
+GFile                  *gtk_source_file_saver_get_location     (GtkSourceFileSaver       *saver);
+
+void                    gtk_source_file_saver_set_encoding     (GtkSourceFileSaver       *saver,
+                                                                const GtkSourceEncoding  *encoding);
+
+const GtkSourceEncoding *gtk_source_file_saver_get_encoding    (GtkSourceFileSaver       *saver);
+
+void                    gtk_source_file_saver_set_newline_type (GtkSourceFileSaver       *saver,
+                                                                GtkSourceNewlineType      newline_type);
+
+GtkSourceNewlineType    gtk_source_file_saver_get_newline_type (GtkSourceFileSaver       *saver);
+
+void                    gtk_source_file_saver_set_compression_type
+                                                               (GtkSourceFileSaver       *saver,
+                                                                GtkSourceCompressionType  compression_type);
+
+GtkSourceCompressionType gtk_source_file_saver_get_compression_type
+                                                               (GtkSourceFileSaver       *saver);
+
+void                    gtk_source_file_saver_set_flags        (GtkSourceFileSaver       *saver,
+                                                                GtkSourceFileSaverFlags   flags);
+
+GtkSourceFileSaverFlags         gtk_source_file_saver_get_flags        (GtkSourceFileSaver       *saver);
+
+void                    gtk_source_file_saver_save_async       (GtkSourceFileSaver       *saver,
+                                                                gint                      io_priority,
+                                                                GCancellable             *cancellable,
+                                                                GFileProgressCallback     progress_callback,
+                                                                gpointer                  
progress_callback_data,
+                                                                GDestroyNotify            
progress_callback_notify,
+                                                                GAsyncReadyCallback       callback,
+                                                                gpointer                  user_data);
+
+gboolean                gtk_source_file_saver_save_finish      (GtkSourceFileSaver       *saver,
+                                                                GAsyncResult             *result,
+                                                                GError                  **error);
+
+G_END_DECLS
+
+#endif  /* __GTK_SOURCE_FILE_SAVER_H__  */
diff --git a/gtksourceview/gtksourcetypes-private.h b/gtksourceview/gtksourcetypes-private.h
index afb1c8a..3c49432 100644
--- a/gtksourceview/gtksourcetypes-private.h
+++ b/gtksourceview/gtksourcetypes-private.h
@@ -26,6 +26,8 @@
 
 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/gtksourceview/gtksourcetypes.h b/gtksourceview/gtksourcetypes.h
index 6442ddd..5c1af72 100644
--- a/gtksourceview/gtksourcetypes.h
+++ b/gtksourceview/gtksourcetypes.h
@@ -2,7 +2,7 @@
  * gtksourcetypes.h
  * This file is part of GtkSourceView
  *
- * Copyright (C) 2012 - Sébastien Wilmet <swilmet gnome org>
+ * Copyright (C) 2012-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
@@ -22,7 +22,7 @@
 #ifndef __GTK_SOURCE_TYPES_H__
 #define __GTK_SOURCE_TYPES_H__
 
-#include <glib.h>
+#include <gio/gio.h>
 
 G_BEGIN_DECLS
 
@@ -33,6 +33,10 @@ typedef struct _GtkSourceCompletionInfo              GtkSourceCompletionInfo;
 typedef struct _GtkSourceCompletionItem                GtkSourceCompletionItem;
 typedef struct _GtkSourceCompletionProposal    GtkSourceCompletionProposal;
 typedef struct _GtkSourceCompletionProvider    GtkSourceCompletionProvider;
+typedef struct _GtkSourceEncoding              GtkSourceEncoding;
+typedef struct _GtkSourceFile                  GtkSourceFile;
+typedef struct _GtkSourceFileLoader            GtkSourceFileLoader;
+typedef struct _GtkSourceFileSaver             GtkSourceFileSaver;
 typedef struct _GtkSourceGutter                        GtkSourceGutter;
 typedef struct _GtkSourceGutterRenderer                GtkSourceGutterRenderer;
 typedef struct _GtkSourceGutterRendererPixbuf  GtkSourceGutterRendererPixbuf;
@@ -50,6 +54,48 @@ typedef struct _GtkSourceStyleSchemeManager  GtkSourceStyleSchemeManager;
 typedef struct _GtkSourceUndoManager           GtkSourceUndoManager;
 typedef struct _GtkSourceView                  GtkSourceView;
 
+/**
+ * GtkSourceNewlineType:
+ * @GTK_SOURCE_NEWLINE_TYPE_LF: line feed, used on UNIX.
+ * @GTK_SOURCE_NEWLINE_TYPE_CR: carriage return, used on Mac.
+ * @GTK_SOURCE_NEWLINE_TYPE_CR_LF: carriage return followed by a line feed, used
+ *   on Windows.
+ *
+ * Since: 3.14
+ */
+typedef enum
+{
+       GTK_SOURCE_NEWLINE_TYPE_LF,
+       GTK_SOURCE_NEWLINE_TYPE_CR,
+       GTK_SOURCE_NEWLINE_TYPE_CR_LF
+} GtkSourceNewlineType;
+
+/**
+ * GTK_SOURCE_NEWLINE_TYPE_DEFAULT:
+ *
+ * The default newline type on the current OS.
+ *
+ * Since: 3.14
+ */
+#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
+
+/**
+ * GtkSourceCompressionType:
+ * @GTK_SOURCE_COMPRESSION_TYPE_NONE: plain text.
+ * @GTK_SOURCE_COMPRESSION_TYPE_GZIP: gzip compression.
+ *
+ * Since: 3.14
+ */
+typedef enum
+{
+       GTK_SOURCE_COMPRESSION_TYPE_NONE,
+       GTK_SOURCE_COMPRESSION_TYPE_GZIP
+} GtkSourceCompressionType;
+
 G_END_DECLS
 
 #endif /* __GTK_SOURCE_TYPES_H__ */
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 6a69647..3658a75 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -120,6 +120,8 @@ data/styles/solarized-light.xml
 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
@@ -128,6 +130,10 @@ gtksourceview/gtksourcecompletionitem.c
 gtksourceview/gtksourcecompletionmodel.c
 [type: gettext/glade]gtksourceview/gtksourcecompletion.ui
 gtksourceview/gtksourcecontextengine.c
+gtksourceview/gtksourceencoding.c
+gtksourceview/gtksourcefile.c
+gtksourceview/gtksourcefileloader.c
+gtksourceview/gtksourcefilesaver.c
 gtksourceview/gtksourcegutter.c
 gtksourceview/gtksourcegutterrenderer.c
 gtksourceview/gtksourcegutterrendererpixbuf.c
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 4fd035c..efa130f 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -68,6 +68,22 @@ test_buffer_LDADD =          \
        $(DEP_LIBS)                     \
        $(TESTS_LIBS)
 
+UNIT_TEST_PROGS += test-buffer-input-stream
+test_buffer_input_stream_SOURCES = test-buffer-input-stream.c
+test_buffer_input_stream_LDADD =                                       \
+       $(top_builddir)/gtksourceview/libgtksourceview-private.la       \
+       $(top_builddir)/gtksourceview/libgtksourceview-3.0.la           \
+       $(DEP_LIBS)                                                     \
+       $(TESTS_LIBS)
+
+UNIT_TEST_PROGS += test-buffer-output-stream
+test_buffer_output_stream_SOURCES = test-buffer-output-stream.c
+test_buffer_output_stream_LDADD =                                      \
+       $(top_builddir)/gtksourceview/libgtksourceview-private.la       \
+       $(top_builddir)/gtksourceview/libgtksourceview-3.0.la           \
+       $(DEP_LIBS)                                                     \
+       $(TESTS_LIBS)
+
 UNIT_TEST_PROGS += test-completion-model
 test_completion_model_SOURCES =        test-completion-model.c
 test_completion_model_LDADD =                                  \
@@ -83,6 +99,20 @@ test_completion_words_LDADD =                                        \
        $(DEP_LIBS)                                             \
        $(TESTS_LIBS)
 
+UNIT_TEST_PROGS += test-file-loader
+test_file_loader_SOURCES = test-file-loader.c
+test_file_loader_LDADD =                                       \
+       $(top_builddir)/gtksourceview/libgtksourceview-3.0.la   \
+       $(DEP_LIBS)                                             \
+       $(TESTS_LIBS)
+
+UNIT_TEST_PROGS += test-file-saver
+test_file_saver_SOURCES = test-file-saver.c
+test_file_saver_LDADD =                                                \
+       $(top_builddir)/gtksourceview/libgtksourceview-3.0.la   \
+       $(DEP_LIBS)                                             \
+       $(TESTS_LIBS)
+
 UNIT_TEST_PROGS += test-language
 test_language_SOURCES =                \
        test-language.c
@@ -168,6 +198,7 @@ TESTS = $(UNIT_TEST_PROGS)
 EXTRA_DIST =                           \
        language-specs/test-empty.lang  \
        language-specs/test-full.lang   \
+       setup-file-saver.sh             \
        styles/classic.xml              \
        test-completion.gresource.xml   \
        test-completion.ui              \
diff --git a/tests/setup-file-saver.sh b/tests/setup-file-saver.sh
new file mode 100755
index 0000000..6c4e37a
--- /dev/null
+++ b/tests/setup-file-saver.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+# This script is used to setup some special directory structures, permissions
+# for the file saver test.
+
+UNOWNED_DIRECTORY="/tmp/gtksourceview-file-saver-unowned"
+UNOWNED_FILE="/tmp/gtksourceview-file-saver-unowned/gtksourceview-file-saver-test.txt"
+
+UNOWNED_GROUP="/tmp/gtksourceview-file-saver-unowned-group.txt"
+
+if [ -f "$UNOWNED_FILE" ]; then
+       sudo rm "$UNOWNED_FILE"
+fi
+
+if [ -d "$UNOWNED_DIRECTORY" ]; then
+       sudo rmdir "$UNOWNED_DIRECTORY"
+fi
+
+mkdir "$UNOWNED_DIRECTORY"
+touch "$UNOWNED_FILE"
+
+sudo chown nobody:nobody "$UNOWNED_DIRECTORY"
+
+sudo touch "$UNOWNED_GROUP"
+sudo chgrp root "$UNOWNED_GROUP"
+sudo chmod u+w,g+w,o-rwx "$UNOWNED_GROUP"
+sudo chown $USER "$UNOWNED_GROUP"
diff --git a/tests/test-buffer-input-stream.c b/tests/test-buffer-input-stream.c
new file mode 100644
index 0000000..218327e
--- /dev/null
+++ b/tests/test-buffer-input-stream.c
@@ -0,0 +1,154 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* test-buffer-input-stream.c
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2010 - Ignacio Casal Quinteiro
+ *
+ * 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 <gio/gio.h>
+#include <gtk/gtk.h>
+#include <string.h>
+#include <gtksourceview/gtksource.h>
+#include "gtksourceview/gtksourcebufferinputstream.h"
+
+static void
+test_consecutive_read (const gchar          *inbuf,
+                      const gchar          *outbuf,
+                      GtkSourceNewlineType  type,
+                      gsize                 read_chunk_len)
+{
+       GtkTextBuffer *buf;
+       GtkSourceBufferInputStream *in;
+       gsize outlen;
+       gssize n, r;
+       GError *err = NULL;
+       gchar *b;
+       gboolean close;
+
+       buf = gtk_text_buffer_new (NULL);
+       gtk_text_buffer_set_text (buf, inbuf, -1);
+
+       b = g_malloc (200);
+       in = _gtk_source_buffer_input_stream_new (buf, type, TRUE);
+
+       outlen = strlen (outbuf);
+       n = 0;
+
+       do
+       {
+               r = g_input_stream_read (G_INPUT_STREAM (in), b + n, read_chunk_len, NULL, &err);
+               g_assert_cmpint (r, >=, 0);
+               g_assert_no_error (err);
+
+               n += r;
+       } while (r != 0);
+
+       g_assert_cmpint (n, ==, outlen);
+
+       b[n] = '\0';
+
+       g_assert_cmpstr (b, ==, outbuf);
+
+       close = g_input_stream_close (G_INPUT_STREAM (in), NULL, &err);
+       g_assert (close);
+       g_assert_no_error (err);
+
+       g_object_unref (buf);
+       g_object_unref (in);
+       g_free (b);
+}
+
+static void
+test_empty (void)
+{
+       /* empty file should not have a trailing newline */
+       test_consecutive_read ("", "", GTK_SOURCE_NEWLINE_TYPE_CR_LF, 10);
+}
+
+static void
+test_consecutive_cut_char (void)
+{
+       /* first \n is read then fo and then is added \r but not \n */
+       test_consecutive_read ("\nfo\nbar\n\nblah\n", "\r\nfo\r\nbar\r\n\r\nblah\r\n\r\n", 
GTK_SOURCE_NEWLINE_TYPE_CR_LF, 8);
+       test_consecutive_read ("\nfo\nbar\n\nblah", "\r\nfo\r\nbar\r\n\r\nblah\r\n", 
GTK_SOURCE_NEWLINE_TYPE_CR_LF, 8);
+}
+
+static void
+test_consecutive_big_read (void)
+{
+       test_consecutive_read ("\nfo\nbar\n\nblah\n", "\rfo\rbar\r\rblah\r\r", GTK_SOURCE_NEWLINE_TYPE_CR, 
200);
+       test_consecutive_read ("\nfo\nbar\n\nblah", "\rfo\rbar\r\rblah\r", GTK_SOURCE_NEWLINE_TYPE_CR, 200);
+
+       test_consecutive_read ("\rfo\rbar\r\rblah\r", "\nfo\nbar\n\nblah\n\n", GTK_SOURCE_NEWLINE_TYPE_LF, 
200);
+       test_consecutive_read ("\rfo\rbar\r\rblah", "\nfo\nbar\n\nblah\n", GTK_SOURCE_NEWLINE_TYPE_LF, 200);
+
+       test_consecutive_read ("\r\nfo\r\nbar\r\n\r\nblah\r\n", "\nfo\nbar\n\nblah\n\n", 
GTK_SOURCE_NEWLINE_TYPE_LF, 200);
+       test_consecutive_read ("\r\nfo\r\nbar\r\n\r\nblah", "\nfo\nbar\n\nblah\n", 
GTK_SOURCE_NEWLINE_TYPE_LF, 200);
+
+       test_consecutive_read ("\nfo\nbar\n\nblah\n", "\r\nfo\r\nbar\r\n\r\nblah\r\n\r\n", 
GTK_SOURCE_NEWLINE_TYPE_CR_LF, 200);
+       test_consecutive_read ("\nfo\nbar\n\nblah", "\r\nfo\r\nbar\r\n\r\nblah\r\n", 
GTK_SOURCE_NEWLINE_TYPE_CR_LF, 200);
+}
+
+static void
+test_consecutive_middle_read (void)
+{
+       test_consecutive_read ("\nfo\nbar\n\nblah\n", "\rfo\rbar\r\rblah\r\r", GTK_SOURCE_NEWLINE_TYPE_CR, 6);
+       test_consecutive_read ("\nfo\nbar\n\nblah", "\rfo\rbar\r\rblah\r", GTK_SOURCE_NEWLINE_TYPE_CR, 6);
+
+       test_consecutive_read ("\rfo\rbar\r\rblah\r", "\nfo\nbar\n\nblah\n\n", GTK_SOURCE_NEWLINE_TYPE_LF, 6);
+       test_consecutive_read ("\rfo\rbar\r\rblah", "\nfo\nbar\n\nblah\n", GTK_SOURCE_NEWLINE_TYPE_LF, 6);
+
+       test_consecutive_read ("\r\nfo\r\nbar\r\n\r\nblah\r\n", "\nfo\nbar\n\nblah\n\n", 
GTK_SOURCE_NEWLINE_TYPE_LF, 6);
+       test_consecutive_read ("\r\nfo\r\nbar\r\n\r\nblah", "\nfo\nbar\n\nblah\n", 
GTK_SOURCE_NEWLINE_TYPE_LF, 6);
+
+       test_consecutive_read ("\nfo\nbar\n\nblah\n", "\r\nfo\r\nbar\r\n\r\nblah\r\n\r\n", 
GTK_SOURCE_NEWLINE_TYPE_CR_LF, 6);
+       test_consecutive_read ("\nfo\nbar\n\nblah", "\r\nfo\r\nbar\r\n\r\nblah\r\n", 
GTK_SOURCE_NEWLINE_TYPE_CR_LF, 6);
+}
+
+static void
+test_consecutive_multibyte_cut (void)
+{
+       test_consecutive_read ("hello\nhello\xe6\x96\x87\nworld\n", "hello\rhello\xe6\x96\x87\rworld\r\r", 
GTK_SOURCE_NEWLINE_TYPE_CR, 6);
+       test_consecutive_read ("hello\rhello\xe6\x96\x87\rworld\r", "hello\rhello\xe6\x96\x87\rworld\r\r", 
GTK_SOURCE_NEWLINE_TYPE_CR, 6);
+       test_consecutive_read ("hello\nhello\xe6\x96\x87\nworld\n", "hello\nhello\xe6\x96\x87\nworld\n\n", 
GTK_SOURCE_NEWLINE_TYPE_LF, 6);
+}
+
+static void
+test_consecutive_multibyte_big_read (void)
+{
+       test_consecutive_read ("hello\nhello\xe6\x96\x87\nworld\n", "hello\rhello\xe6\x96\x87\rworld\r\r", 
GTK_SOURCE_NEWLINE_TYPE_CR, 200);
+       test_consecutive_read ("hello\rhello\xe6\x96\x87\rworld\r", "hello\rhello\xe6\x96\x87\rworld\r\r", 
GTK_SOURCE_NEWLINE_TYPE_CR, 200);
+       test_consecutive_read ("hello\nhello\xe6\x96\x87\nworld\n", "hello\nhello\xe6\x96\x87\nworld\n\n", 
GTK_SOURCE_NEWLINE_TYPE_LF, 200);
+}
+
+gint
+main (gint   argc,
+      gchar *argv[])
+{
+       g_test_init (&argc, &argv, NULL);
+
+       g_test_add_func ("/buffer-input-stream/empty", test_empty);
+
+       g_test_add_func ("/buffer-input-stream/consecutive_cut_char", test_consecutive_cut_char);
+       g_test_add_func ("/buffer-input-stream/consecutive_big_read", test_consecutive_big_read);
+       g_test_add_func ("/buffer-input-stream/consecutive_middle_read", test_consecutive_middle_read);
+
+       g_test_add_func ("/buffer-input-stream/consecutive_multibyte_cut", test_consecutive_multibyte_cut);
+       g_test_add_func ("/buffer-input-stream/consecutive_multibyte_big_read", 
test_consecutive_multibyte_big_read);
+
+       return g_test_run ();
+}
diff --git a/tests/test-buffer-output-stream.c b/tests/test-buffer-output-stream.c
new file mode 100644
index 0000000..97e72c3
--- /dev/null
+++ b/tests/test-buffer-output-stream.c
@@ -0,0 +1,425 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* test-buffer-output-stream.c
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2010 - Ignacio Casal Quinteiro
+ * Copyright (C) 2014 - Sébastien Wilmet
+ *
+ * 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 <glib.h>
+#include <glib/gprintf.h>
+#include <gtk/gtk.h>
+#include <string.h>
+#include <gtksourceview/gtksource.h>
+#include "gtksourceview/gtksourcebufferoutputstream.h"
+
+static void
+test_consecutive_write (const gchar          *inbuf,
+                       const gchar          *outbuf,
+                       gsize                 write_chunk_len,
+                       GtkSourceNewlineType  newline_type)
+{
+       GtkSourceBuffer *source_buffer;
+       GtkSourceBufferOutputStream *out;
+       gsize len;
+       gssize n, w;
+       GError *err = NULL;
+       gchar *b;
+       GtkSourceNewlineType type;
+       GSList *encodings = NULL;
+
+       source_buffer = gtk_source_buffer_new (NULL);
+       encodings = g_slist_prepend (encodings, (gpointer)gtk_source_encoding_get_utf8 ());
+       out = gtk_source_buffer_output_stream_new (source_buffer, encodings, TRUE);
+
+       n = 0;
+
+       do
+       {
+               len = MIN (write_chunk_len, strlen (inbuf + n));
+               w = g_output_stream_write (G_OUTPUT_STREAM (out), inbuf + n, len, NULL, &err);
+               g_assert_cmpint (w, >=, 0);
+               g_assert_no_error (err);
+
+               n += w;
+       } while (w != 0);
+
+       g_output_stream_flush (G_OUTPUT_STREAM (out), NULL, &err);
+
+       g_assert_no_error (err);
+
+       type = gtk_source_buffer_output_stream_detect_newline_type (out);
+       g_assert (type == newline_type);
+
+       g_output_stream_close (G_OUTPUT_STREAM (out), NULL, &err);
+       g_assert_no_error (err);
+
+       g_object_get (G_OBJECT (source_buffer), "text", &b, NULL);
+
+       g_assert_cmpstr (outbuf, ==, b);
+       g_free (b);
+
+       g_assert (gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (source_buffer)) == FALSE);
+
+       g_object_unref (source_buffer);
+       g_object_unref (out);
+}
+
+static void
+test_empty (void)
+{
+       test_consecutive_write ("", "", 10, GTK_SOURCE_NEWLINE_TYPE_DEFAULT);
+       test_consecutive_write ("\r\n", "", 10, GTK_SOURCE_NEWLINE_TYPE_CR_LF);
+       test_consecutive_write ("\r", "", 10, GTK_SOURCE_NEWLINE_TYPE_CR);
+       test_consecutive_write ("\n", "", 10, GTK_SOURCE_NEWLINE_TYPE_LF);
+}
+
+static void
+test_consecutive (void)
+{
+       test_consecutive_write ("hello\nhow\nare\nyou", "hello\nhow\nare\nyou", 2,
+                               GTK_SOURCE_NEWLINE_TYPE_LF);
+       test_consecutive_write ("hello\rhow\rare\ryou", "hello\rhow\rare\ryou", 2,
+                               GTK_SOURCE_NEWLINE_TYPE_CR);
+       test_consecutive_write ("hello\r\nhow\r\nare\r\nyou", "hello\r\nhow\r\nare\r\nyou", 2,
+                               GTK_SOURCE_NEWLINE_TYPE_CR_LF);
+}
+
+static void
+test_consecutive_tnewline (void)
+{
+       test_consecutive_write ("hello\nhow\nare\nyou\n", "hello\nhow\nare\nyou", 2,
+                               GTK_SOURCE_NEWLINE_TYPE_LF);
+       test_consecutive_write ("hello\rhow\rare\ryou\r", "hello\rhow\rare\ryou", 2,
+                               GTK_SOURCE_NEWLINE_TYPE_CR);
+       test_consecutive_write ("hello\r\nhow\r\nare\r\nyou\r\n", "hello\r\nhow\r\nare\r\nyou", 2,
+                               GTK_SOURCE_NEWLINE_TYPE_CR_LF);
+}
+
+static void
+test_big_char (void)
+{
+       test_consecutive_write ("\343\203\200\343\203\200", "\343\203\200\343\203\200", 2,
+                               GTK_SOURCE_NEWLINE_TYPE_LF);
+}
+
+static void
+test_boundary (void)
+{
+       GtkSourceBuffer *source_buffer;
+       GtkSourceBufferOutputStream *out;
+       gint line_count;
+       GError *err = NULL;
+       GSList *encodings = NULL;
+
+       source_buffer = gtk_source_buffer_new (NULL);
+       encodings = g_slist_prepend (encodings, (gpointer)gtk_source_encoding_get_utf8 ());
+       out = gtk_source_buffer_output_stream_new (source_buffer, encodings, TRUE);
+
+       g_output_stream_write (G_OUTPUT_STREAM (out), "\r", 1, NULL, NULL);
+       g_output_stream_write (G_OUTPUT_STREAM (out), "\n", 1, NULL, NULL);
+
+       g_output_stream_flush (G_OUTPUT_STREAM (out), NULL, &err);
+       g_assert_no_error (err);
+
+       line_count = gtk_text_buffer_get_line_count (GTK_TEXT_BUFFER (source_buffer));
+
+       g_assert_cmpint (line_count, ==, 2);
+
+       g_output_stream_close (G_OUTPUT_STREAM (out), NULL, &err);
+       g_assert_no_error (err);
+
+       g_object_unref (source_buffer);
+       g_object_unref (out);
+}
+
+#if 0
+static void
+test_invalid_utf8 (void)
+{
+       test_consecutive_write ("foobar\n\xef\xbf\xbe", "foobar\n\\EF\\BF\\BE", 10,
+                               GTK_SOURCE_NEWLINE_TYPE_LF);
+       test_consecutive_write ("foobar\n\xef\xbf\xbezzzzzz\n", "foobar\n\\EF\\BF\\BEzzzzzz", 10,
+                               GTK_SOURCE_NEWLINE_TYPE_LF);
+       test_consecutive_write ("\xef\xbf\xbezzzzzz\n", "\\EF\\BF\\BEzzzzzz", 10,
+                               GTK_SOURCE_NEWLINE_TYPE_LF);
+}
+#endif
+
+/* SMART CONVERSION */
+
+#define TEXT_TO_CONVERT "this is some text to make the tests"
+#define TEXT_TO_GUESS "hello \xe6\x96\x87 world"
+
+static gchar *
+get_encoded_text (const gchar             *text,
+                 gsize                    nread,
+                 const GtkSourceEncoding *to,
+                 const GtkSourceEncoding *from,
+                 gsize                   *bytes_written_aux,
+                 gboolean                 care_about_error)
+{
+       GCharsetConverter *converter;
+       gchar *out, *out_aux;
+       gsize bytes_read, bytes_read_aux;
+       gsize bytes_written;
+       GConverterResult res;
+       GError *err;
+
+       converter = g_charset_converter_new (gtk_source_encoding_get_charset (to),
+                                            gtk_source_encoding_get_charset (from),
+                                            NULL);
+
+       out = g_malloc (200);
+       out_aux = g_malloc (200);
+       err = NULL;
+       bytes_read_aux = 0;
+       *bytes_written_aux = 0;
+
+       if (nread == -1)
+       {
+               nread = strlen (text);
+       }
+
+       do
+       {
+               res = g_converter_convert (G_CONVERTER (converter),
+                                          text + bytes_read_aux,
+                                          nread,
+                                          out_aux,
+                                          200,
+                                          G_CONVERTER_INPUT_AT_END,
+                                          &bytes_read,
+                                          &bytes_written,
+                                          &err);
+               memcpy (out + *bytes_written_aux, out_aux, bytes_written);
+               bytes_read_aux += bytes_read;
+               *bytes_written_aux += bytes_written;
+               nread -= bytes_read;
+       } while (res != G_CONVERTER_FINISHED && res != G_CONVERTER_ERROR);
+
+       if (care_about_error)
+       {
+               g_assert_no_error (err);
+       }
+       else if (err)
+       {
+               g_printf ("** You don't care, but there was an error: %s", err->message);
+               return NULL;
+       }
+
+       out[*bytes_written_aux] = '\0';
+
+       if (!g_utf8_validate (out, *bytes_written_aux, NULL) && !care_about_error)
+       {
+               if (!care_about_error)
+               {
+                       return NULL;
+               }
+               else
+               {
+                       g_assert_not_reached ();
+               }
+       }
+
+       return out;
+}
+
+static gchar *
+do_test (const gchar              *inbuf,
+        const gchar              *enc,
+        GSList                   *encodings,
+        gsize                     len,
+        gsize                     write_chunk_len,
+        const GtkSourceEncoding **guessed)
+{
+       GtkSourceBuffer *source_buffer;
+       GtkSourceBufferOutputStream *out;
+       GError *err = NULL;
+       GtkTextIter start, end;
+       gchar *text;
+       gsize to_write;
+       gssize n, w;
+
+       if (enc != NULL)
+       {
+               encodings = NULL;
+               encodings = g_slist_prepend (encodings, (gpointer)gtk_source_encoding_get_from_charset (enc));
+       }
+
+       source_buffer = gtk_source_buffer_new (NULL);
+       out = gtk_source_buffer_output_stream_new (source_buffer, encodings, TRUE);
+
+       n = 0;
+
+       do
+       {
+               to_write = MIN (len, write_chunk_len);
+               w = g_output_stream_write (G_OUTPUT_STREAM (out), inbuf + n, to_write, NULL, &err);
+               g_assert_cmpint (w, >=, 0);
+               g_assert_no_error (err);
+
+               len -= w;
+               n += w;
+       } while (len != 0);
+
+       g_output_stream_flush (G_OUTPUT_STREAM (out), NULL, &err);
+       g_assert_no_error (err);
+
+       g_output_stream_close (G_OUTPUT_STREAM (out), NULL, &err);
+       g_assert_no_error (err);
+
+       if (guessed != NULL)
+       {
+               *guessed = gtk_source_buffer_output_stream_get_guessed (out);
+       }
+
+       gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (source_buffer), &start, &end);
+       text = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (source_buffer),
+                                        &start,
+                                        &end,
+                                        FALSE);
+
+       g_object_unref (source_buffer);
+       g_object_unref (out);
+
+       return text;
+}
+
+static void
+test_utf8_utf8 (void)
+{
+       gchar *aux;
+
+       aux = do_test (TEXT_TO_CONVERT, "UTF-8", NULL, strlen (TEXT_TO_CONVERT), strlen (TEXT_TO_CONVERT), 
NULL);
+       g_assert_cmpstr (aux, ==, TEXT_TO_CONVERT);
+       g_free (aux);
+
+       aux = do_test ("foobar\xc3\xa8\xc3\xa8\xc3\xa8zzzzzz", "UTF-8", NULL, 18, 18, NULL);
+       g_assert_cmpstr (aux, ==, "foobar\xc3\xa8\xc3\xa8\xc3\xa8zzzzzz");
+       g_free (aux);
+
+       /* small chunk */
+       aux = do_test ("foobar\xc3\xa8\xc3\xa8\xc3\xa8zzzzzz", "UTF-8", NULL, 18, 2, NULL);
+       g_assert_cmpstr (aux, ==, "foobar\xc3\xa8\xc3\xa8\xc3\xa8zzzzzz");
+       g_free (aux);
+}
+
+static void
+test_empty_conversion (void)
+{
+       const GtkSourceEncoding *guessed;
+       gchar *out;
+       GSList *encodings = NULL;
+
+       /* testing the case of an empty file and list of encodings with no
+          utf-8. In this case, the smart converter cannot determine the right
+          encoding (because there is no input), but should still default to
+          utf-8 for the detection */
+       encodings = g_slist_prepend (encodings, (gpointer)gtk_source_encoding_get_from_charset ("UTF-16"));
+       encodings = g_slist_prepend (encodings, (gpointer)gtk_source_encoding_get_from_charset 
("ISO-8859-15"));
+
+       out = do_test ("", NULL, encodings, 0, 0, &guessed);
+
+       g_assert_cmpstr (out, ==, "");
+       g_free (out);
+
+       g_assert (guessed == gtk_source_encoding_get_utf8 ());
+}
+
+static void
+test_guessed (void)
+{
+       GSList *encs = NULL;
+       gchar *aux, *aux2, *fail;
+       gsize aux_len, fail_len;
+       const GtkSourceEncoding *guessed;
+
+       aux = get_encoded_text (TEXT_TO_GUESS, -1,
+                               gtk_source_encoding_get_from_charset ("UTF-16"),
+                               gtk_source_encoding_get_from_charset ("UTF-8"),
+                               &aux_len,
+                               TRUE);
+
+       fail = get_encoded_text (aux, aux_len,
+                                gtk_source_encoding_get_from_charset ("UTF-8"),
+                                gtk_source_encoding_get_from_charset ("ISO-8859-15"),
+                                &fail_len,
+                                FALSE);
+
+       g_assert (fail == NULL);
+
+       /* ISO-8859-15 should fail */
+       encs = g_slist_append (encs, (gpointer)gtk_source_encoding_get_from_charset ("ISO-8859-15"));
+       encs = g_slist_append (encs, (gpointer)gtk_source_encoding_get_from_charset ("UTF-16"));
+
+       aux2 = do_test (aux, NULL, encs, aux_len, aux_len, &guessed);
+       g_free (aux);
+       g_free (aux2);
+
+       g_assert (guessed == gtk_source_encoding_get_from_charset ("UTF-16"));
+}
+
+static void
+test_utf16_utf8 (void)
+{
+       gchar *text, *aux;
+       gsize aux_len;
+
+       text = get_encoded_text ("\xe2\xb4\xb2", -1,
+                                gtk_source_encoding_get_from_charset ("UTF-16"),
+                                gtk_source_encoding_get_from_charset ("UTF-8"),
+                                &aux_len,
+                                TRUE);
+
+       aux = do_test (text, "UTF-16", NULL, aux_len, aux_len, NULL);
+       g_assert_cmpstr (aux, ==, "\xe2\xb4\xb2");
+       g_free (aux);
+
+       aux = do_test (text, "UTF-16", NULL, aux_len, 1, NULL);
+       g_assert_cmpstr (aux, ==, "\xe2\xb4\xb2");
+       g_free (aux);
+}
+
+gint
+main (gint   argc,
+      gchar *argv[])
+{
+       g_test_init (&argc, &argv, NULL);
+
+       g_test_add_func ("/buffer-output-stream/empty", test_empty);
+
+       g_test_add_func ("/buffer-output-stream/consecutive", test_consecutive);
+       g_test_add_func ("/buffer-output-stream/consecutive_tnewline", test_consecutive_tnewline);
+       g_test_add_func ("/buffer-output-stream/big-char", test_big_char);
+       g_test_add_func ("/buffer-output-stream/test-boundary", test_boundary);
+
+
+       /* This broke after https://bugzilla.gnome.org/show_bug.cgi?id=694669 We
+        * need to revisit the test to pick something that is actually invalid
+        * utf8.
+        */
+#if 0
+       g_test_add_func ("/buffer-output-stream/test-invalid-utf8", test_invalid_utf8);
+#endif
+       g_test_add_func ("/buffer-output-stream/smart conversion: utf8-utf8", test_utf8_utf8);
+       g_test_add_func ("/buffer-output-stream/smart conversion: empty", test_empty_conversion);
+       g_test_add_func ("/buffer-output-stream/smart conversion: guessed", test_guessed);
+       g_test_add_func ("/buffer-output-stream/smart conversion: utf16-utf8", test_utf16_utf8);
+
+       return g_test_run ();
+}
diff --git a/tests/test-file-loader.c b/tests/test-file-loader.c
new file mode 100644
index 0000000..fe9cfa8
--- /dev/null
+++ b/tests/test-file-loader.c
@@ -0,0 +1,236 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* test-file-loader.c
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2010 - Jesse van den Kieboom
+ * Copyright (C) 2014 - Sébastien Wilmet
+ *
+ * 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 <gtksourceview/gtksource.h>
+#include <string.h>
+
+typedef struct
+{
+       const gchar *expected_buffer_contents;
+       gint newline_type;
+} LoaderTestData;
+
+static void
+delete_file (GFile *location)
+{
+       if (g_file_query_exists (location, NULL))
+       {
+               GError *error = NULL;
+
+               g_file_delete (location, NULL, &error);
+               g_assert_no_error (error);
+       }
+}
+
+static void
+load_file_cb (GtkSourceFileLoader *loader,
+             GAsyncResult        *result,
+             LoaderTestData      *data)
+{
+       GError *error = NULL;
+
+       gtk_source_file_loader_load_finish (loader, result, &error);
+       g_assert_no_error (error);
+
+       if (data->expected_buffer_contents != NULL)
+       {
+               GtkSourceBuffer *buffer;
+               GtkTextIter start;
+               GtkTextIter end;
+               gchar *buffer_contents;
+
+               buffer = gtk_source_file_loader_get_buffer (loader);
+
+               gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer), &start, &end);
+               buffer_contents = gtk_text_iter_get_slice (&start, &end);
+
+               g_assert_cmpstr (buffer_contents, ==, data->expected_buffer_contents);
+
+               g_free (buffer_contents);
+       }
+
+       if (data->newline_type != -1)
+       {
+               g_assert_cmpint (gtk_source_file_loader_get_newline_type (loader),
+                                ==,
+                                data->newline_type);
+       }
+
+       /* finished */
+       gtk_main_quit ();
+}
+
+static void
+test_loader (const gchar *filename,
+             const gchar *contents,
+             const gchar *expected_buffer_contents,
+             gint         newline_type)
+{
+       GFile *location;
+       GtkSourceBuffer *buffer;
+       GtkSourceFile *file;
+       GtkSourceFileLoader *loader;
+       GSList *candidate_encodings;
+       LoaderTestData *data;
+       GError *error = NULL;
+
+       g_file_set_contents (filename, contents, -1, &error);
+       g_assert_no_error (error);
+
+       location = g_file_new_for_path (filename);
+       buffer = gtk_source_buffer_new (NULL);
+       file = gtk_source_file_new ();
+       loader = gtk_source_file_loader_new (buffer, file, location);
+
+       candidate_encodings = g_slist_prepend (NULL, (gpointer) gtk_source_encoding_get_utf8 ());
+       gtk_source_file_loader_set_candidate_encodings (loader, candidate_encodings);
+
+       data = g_slice_new (LoaderTestData);
+       data->expected_buffer_contents = expected_buffer_contents;
+       data->newline_type = newline_type;
+
+       gtk_source_file_loader_load_async (loader,
+                                          G_PRIORITY_DEFAULT,
+                                          NULL, NULL, NULL, NULL,
+                                          (GAsyncReadyCallback) load_file_cb,
+                                          data);
+
+       gtk_main ();
+
+       g_slice_free (LoaderTestData, data);
+       delete_file (location);
+       g_object_unref (location);
+       g_object_unref (buffer);
+       g_object_unref (file);
+       g_object_unref (loader);
+       g_slist_free (candidate_encodings);
+}
+
+static void
+test_end_line_stripping (void)
+{
+       test_loader ("file-loader.txt",
+                    "hello world\n",
+                    "hello world",
+                    -1);
+
+       test_loader ("file-loader.txt",
+                    "hello world",
+                    "hello world",
+                    -1);
+
+       test_loader ("file-loader.txt",
+                    "\nhello world",
+                    "\nhello world",
+                    -1);
+
+       test_loader ("file-loader.txt",
+                    "\nhello world\n",
+                    "\nhello world",
+                    -1);
+
+       test_loader ("file-loader.txt",
+                    "hello world\n\n",
+                    "hello world\n",
+                    -1);
+
+       test_loader ("file-loader.txt",
+                    "hello world\r\n",
+                    "hello world",
+                    -1);
+
+       test_loader ("file-loader.txt",
+                    "hello world\r\n\r\n",
+                    "hello world\r\n",
+                    -1);
+
+       test_loader ("file-loader.txt",
+                    "\n",
+                    "",
+                    -1);
+
+       test_loader ("file-loader.txt",
+                    "\r\n",
+                    "",
+                    -1);
+
+       test_loader ("file-loader.txt",
+                    "\n\n",
+                    "\n",
+                    -1);
+
+       test_loader ("file-loader.txt",
+                    "\r\n\r\n",
+                    "\r\n",
+                    -1);
+}
+
+static void
+test_end_new_line_detection (void)
+{
+       test_loader ("file-loader.txt",
+                    "hello world\n",
+                    NULL,
+                    GTK_SOURCE_NEWLINE_TYPE_LF);
+
+       test_loader ("file-loader.txt",
+                    "hello world\r\n",
+                    NULL,
+                    GTK_SOURCE_NEWLINE_TYPE_CR_LF);
+
+       test_loader ("file-loader.txt",
+                    "hello world\r",
+                    NULL,
+                    GTK_SOURCE_NEWLINE_TYPE_CR);
+}
+
+static void
+test_begin_new_line_detection (void)
+{
+       test_loader ("file-loader.txt",
+                    "\nhello world",
+                    NULL,
+                    GTK_SOURCE_NEWLINE_TYPE_LF);
+
+       test_loader ("file-loader.txt",
+                    "\r\nhello world",
+                    NULL,
+                    GTK_SOURCE_NEWLINE_TYPE_CR_LF);
+
+       test_loader ("file-loader.txt",
+                    "\rhello world",
+                    NULL,
+                    GTK_SOURCE_NEWLINE_TYPE_CR);
+}
+
+gint
+main (gint   argc,
+      gchar *argv[])
+{
+       g_test_init (&argc, &argv, NULL);
+
+       g_test_add_func ("/file-loader/end-line-stripping", test_end_line_stripping);
+       g_test_add_func ("/file-loader/end-new-line-detection", test_end_new_line_detection);
+       g_test_add_func ("/file-loader/begin-new-line-detection", test_begin_new_line_detection);
+
+       return g_test_run ();
+}
diff --git a/tests/test-file-saver.c b/tests/test-file-saver.c
new file mode 100644
index 0000000..5b0783b
--- /dev/null
+++ b/tests/test-file-saver.c
@@ -0,0 +1,758 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* test-file-saver.c
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2010 - Jesse van den Kieboom
+ * Copyright (C) 2014 - Sébastien Wilmet
+ *
+ * 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 <gio/gio.h>
+#include <gtk/gtk.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <glib/gprintf.h>
+#include <gtksourceview/gtksource.h>
+
+/* linux/bsd has it. others such as Solaris, do not */
+#ifndef ACCESSPERMS
+#define ACCESSPERMS (S_IRWXU|S_IRWXG|S_IRWXO)
+#endif
+
+#define DEFAULT_LOCAL_URI "/tmp/gtksourceview-file-saver-test.txt"
+#define DEFAULT_REMOTE_URI "sftp://localhost/tmp/gtksourceview-file-saver-test.txt";
+#define DEFAULT_CONTENT "hello world!"
+#define DEFAULT_CONTENT_RESULT "hello world!\n"
+
+#define UNOWNED_LOCAL_DIRECTORY "/tmp/gtksourceview-file-saver-unowned"
+#define UNOWNED_LOCAL_URI "/tmp/gtksourceview-file-saver-unowned/gtksourceview-file-saver-test.txt"
+
+#define UNOWNED_REMOTE_DIRECTORY "sftp://localhost/tmp/gtksourceview-file-saver-unowned";
+#define UNOWNED_REMOTE_URI 
"sftp://localhost/tmp/gtksourceview-file-saver-unowned/gtksourceview-file-saver-test.txt";
+
+#define UNOWNED_GROUP_LOCAL_URI "/tmp/gtksourceview-file-saver-unowned-group.txt"
+#define UNOWNED_GROUP_REMOTE_URI "sftp://localhost/tmp/gtksourceview-file-saver-unowned-group.txt";
+
+typedef struct _SaverTestData SaverTestData;
+typedef void (*SavedCallback) (SaverTestData *data);
+
+struct _SaverTestData
+{
+       GtkSourceFileSaver *saver;
+       GFile *location;
+       const gchar *expected_file_contents;
+       SavedCallback saved_callback;
+       gpointer userdata;
+
+       guint file_existed : 1;
+};
+
+static const gchar *
+read_file (GFile *location)
+{
+       /* TODO use g_file_load_contents() */
+       GError *error = NULL;
+       static gchar buffer[4096];
+       gsize read;
+
+       GInputStream *stream = G_INPUT_STREAM (g_file_read (location, NULL, &error));
+
+       g_assert_no_error (error);
+
+       g_input_stream_read_all (stream, buffer, sizeof (buffer) - 1, &read, NULL, &error);
+       g_assert_no_error (error);
+
+       buffer[read] = '\0';
+
+       g_input_stream_close (stream, NULL, NULL);
+
+       g_object_unref (stream);
+
+       return buffer;
+}
+
+static void
+save_file_cb (GtkSourceFileSaver *saver,
+             GAsyncResult       *result,
+             SaverTestData      *data)
+{
+       GError *error = NULL;
+
+       gtk_source_file_saver_save_finish (saver, result, &error);
+
+       g_assert_no_error (error);
+
+       g_assert_cmpstr (data->expected_file_contents, ==, read_file (data->location));
+
+       if (data->saved_callback != NULL)
+       {
+               data->saved_callback (data);
+       }
+
+       if (!data->file_existed)
+       {
+               g_file_delete (data->location, NULL, NULL);
+       }
+
+       /* finished */
+       gtk_main_quit ();
+}
+
+static void
+save_file (SaverTestData *data)
+{
+       data->file_existed = g_file_query_exists (data->location, NULL);
+
+       gtk_source_file_saver_save_async (data->saver,
+                                         G_PRIORITY_DEFAULT,
+                                         NULL, NULL, NULL, NULL,
+                                         (GAsyncReadyCallback) save_file_cb,
+                                         data);
+}
+
+static void
+mount_cb (GFile         *location,
+         GAsyncResult  *result,
+         SaverTestData *data)
+{
+       GError *error = NULL;
+
+       g_file_mount_enclosing_volume_finish (location, result, &error);
+
+       if (error != NULL && error->code == G_IO_ERROR_ALREADY_MOUNTED)
+       {
+               g_error_free (error);
+       }
+       else if (error != NULL && error->code == G_IO_ERROR_NOT_SUPPORTED)
+       {
+               g_error_free (error);
+
+               /* The unit test can not be run */
+               gtk_main_quit ();
+               return;
+       }
+       else
+       {
+               g_assert_no_error (error);
+       }
+
+       save_file (data);
+}
+
+static void
+check_mounted (SaverTestData *data)
+{
+       GMountOperation *mount_operation;
+
+       if (g_file_is_native (data->location))
+       {
+               save_file (data);
+               return;
+       }
+
+       mount_operation = gtk_mount_operation_new (NULL);
+
+       g_file_mount_enclosing_volume (data->location,
+                                      G_MOUNT_MOUNT_NONE,
+                                      mount_operation,
+                                      NULL,
+                                      (GAsyncReadyCallback) mount_cb,
+                                      data);
+
+       g_object_unref (mount_operation);
+}
+
+static void
+test_saver (const gchar            *filename_or_uri,
+           const gchar            *buffer_contents,
+           const gchar            *expected_file_contents,
+           GtkSourceNewlineType    newline_type,
+           SavedCallback           saved_callback,
+           gpointer                userdata)
+{
+       GFile *location;
+       GtkSourceBuffer *buffer;
+       GtkSourceFile *file;
+       GtkSourceFileSaver *saver;
+       SaverTestData *data;
+
+       location = g_file_new_for_commandline_arg (filename_or_uri);
+
+       buffer = gtk_source_buffer_new (NULL);
+       gtk_text_buffer_set_text (GTK_TEXT_BUFFER (buffer), buffer_contents, -1);
+
+       file = gtk_source_file_new ();
+       saver = gtk_source_file_saver_new (buffer, file, location);
+
+       gtk_source_file_saver_set_newline_type (saver, newline_type);
+       gtk_source_file_saver_set_encoding (saver, gtk_source_encoding_get_utf8 ());
+
+       data = g_slice_new (SaverTestData);
+       data->saver = saver;
+       data->location = location;
+       data->expected_file_contents = expected_file_contents;
+       data->saved_callback = saved_callback;
+       data->userdata = userdata;
+
+       check_mounted (data);
+       gtk_main ();
+
+       g_object_unref (location);
+       g_object_unref (buffer);
+       g_object_unref (file);
+       g_object_unref (saver);
+       g_slice_free (SaverTestData, data);
+}
+
+typedef struct
+{
+       GtkSourceNewlineType type;
+       const gchar *text;
+       const gchar *result;
+} NewLineTestData;
+
+static NewLineTestData newline_test_data[] = {
+       {GTK_SOURCE_NEWLINE_TYPE_LF, "\nhello\nworld", "\nhello\nworld\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_LF, "\nhello\nworld\n", "\nhello\nworld\n\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_LF, "\nhello\nworld\n\n", "\nhello\nworld\n\n\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_LF, "\r\nhello\r\nworld", "\nhello\nworld\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_LF, "\r\nhello\r\nworld\r\n", "\nhello\nworld\n\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_LF, "\rhello\rworld", "\nhello\nworld\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_LF, "\rhello\rworld\r", "\nhello\nworld\n\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_LF, "\nhello\r\nworld", "\nhello\nworld\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_LF, "\nhello\r\nworld\r", "\nhello\nworld\n\n"},
+
+       {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\nhello\nworld", "\r\nhello\r\nworld\r\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\nhello\nworld\n", "\r\nhello\r\nworld\r\n\r\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\nhello\nworld\n\n", "\r\nhello\r\nworld\r\n\r\n\r\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\r\nhello\r\nworld", "\r\nhello\r\nworld\r\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\r\nhello\r\nworld\r\n", "\r\nhello\r\nworld\r\n\r\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\rhello\rworld", "\r\nhello\r\nworld\r\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\rhello\rworld\r", "\r\nhello\r\nworld\r\n\r\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\nhello\r\nworld", "\r\nhello\r\nworld\r\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\nhello\r\nworld\r", "\r\nhello\r\nworld\r\n\r\n"},
+
+       {GTK_SOURCE_NEWLINE_TYPE_CR, "\nhello\nworld", "\rhello\rworld\r"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR, "\nhello\nworld\n", "\rhello\rworld\r\r"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR, "\nhello\nworld\n\n", "\rhello\rworld\r\r\r"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR, "\r\nhello\r\nworld", "\rhello\rworld\r"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR, "\r\nhello\r\nworld\r\n", "\rhello\rworld\r\r"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR, "\rhello\rworld", "\rhello\rworld\r"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR, "\rhello\rworld\r", "\rhello\rworld\r\r"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR, "\nhello\r\nworld", "\rhello\rworld\r"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR, "\nhello\r\nworld\r", "\rhello\rworld\r\r"}
+};
+
+static void
+test_new_line (const gchar *filename)
+{
+       gint i;
+       gint num = sizeof (newline_test_data) / sizeof (NewLineTestData);
+
+       for (i = 0; i < num; ++i)
+       {
+               NewLineTestData *nt = &(newline_test_data[i]);
+
+               test_saver (filename,
+                           nt->text,
+                           nt->result,
+                           nt->type,
+                           NULL,
+                           NULL);
+       }
+}
+
+static void
+test_local_newline (void)
+{
+       test_new_line (DEFAULT_LOCAL_URI);
+}
+
+static void
+test_local (void)
+{
+       test_saver (DEFAULT_LOCAL_URI,
+                   "hello world",
+                   "hello world\n",
+                   GTK_SOURCE_NEWLINE_TYPE_LF,
+                   NULL,
+                   NULL);
+
+       test_saver (DEFAULT_LOCAL_URI,
+                   "hello world\r\n",
+                   "hello world\n\n",
+                   GTK_SOURCE_NEWLINE_TYPE_LF,
+                   NULL,
+                   NULL);
+
+       test_saver (DEFAULT_LOCAL_URI,
+                   "hello world\n",
+                   "hello world\n\n",
+                   GTK_SOURCE_NEWLINE_TYPE_LF,
+                   NULL,
+                   NULL);
+}
+
+static void
+test_remote_newline (void)
+{
+       test_new_line (DEFAULT_REMOTE_URI);
+}
+
+static void
+test_remote (void)
+{
+       test_saver (DEFAULT_REMOTE_URI,
+                   "hello world",
+                   "hello world\n",
+                   GTK_SOURCE_NEWLINE_TYPE_LF,
+                   NULL,
+                   NULL);
+
+       test_saver (DEFAULT_REMOTE_URI,
+                   "hello world\r\n",
+                   "hello world\n\n",
+                   GTK_SOURCE_NEWLINE_TYPE_LF,
+                   NULL,
+                   NULL);
+
+       test_saver (DEFAULT_REMOTE_URI,
+                   "hello world\n",
+                   "hello world\n\n",
+                   GTK_SOURCE_NEWLINE_TYPE_LF,
+                   NULL,
+                   NULL);
+}
+
+#ifndef G_OS_WIN32
+static void
+check_permissions (GFile *location,
+                   guint  permissions)
+{
+       GError *error = NULL;
+       GFileInfo *info;
+
+       info = g_file_query_info (location,
+                                 G_FILE_ATTRIBUTE_UNIX_MODE,
+                                 G_FILE_QUERY_INFO_NONE,
+                                 NULL,
+                                 &error);
+
+       g_assert_no_error (error);
+
+       g_assert_cmpint (permissions,
+                        ==,
+                        g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE) & ACCESSPERMS);
+
+       g_object_unref (info);
+}
+
+static void
+check_permissions_saved (SaverTestData *data)
+{
+       guint permissions = (guint)GPOINTER_TO_INT (data->userdata);
+
+       check_permissions (data->location, permissions);
+}
+
+static void
+test_permissions (const gchar *uri,
+                  guint        permissions)
+{
+       GError *error = NULL;
+       GFile *location = g_file_new_for_commandline_arg (uri);
+       GFileOutputStream *stream;
+       GFileInfo *info;
+       guint mode;
+
+       g_file_delete (location, NULL, NULL);
+       stream = g_file_create (location, 0, NULL, &error);
+
+       if (error && error->code == G_IO_ERROR_NOT_SUPPORTED)
+       {
+               g_error_free (error);
+               return;
+       }
+
+       g_assert_no_error (error);
+
+       g_output_stream_close (G_OUTPUT_STREAM (stream), NULL, NULL);
+       g_object_unref (stream);
+
+       info = g_file_query_info (location,
+                                 G_FILE_ATTRIBUTE_UNIX_MODE,
+                                 G_FILE_QUERY_INFO_NONE,
+                                 NULL,
+                                 &error);
+
+       g_assert_no_error (error);
+
+       mode = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE);
+       g_object_unref (info);
+
+       g_file_set_attribute_uint32 (location,
+                                    G_FILE_ATTRIBUTE_UNIX_MODE,
+                                    (mode & ~ACCESSPERMS) | permissions,
+                                    G_FILE_QUERY_INFO_NONE,
+                                    NULL,
+                                    &error);
+       g_assert_no_error (error);
+
+       check_permissions (location, permissions);
+
+       test_saver (uri,
+                   DEFAULT_CONTENT,
+                   DEFAULT_CONTENT_RESULT,
+                   GTK_SOURCE_NEWLINE_TYPE_LF,
+                   check_permissions_saved,
+                   GINT_TO_POINTER ((gint)permissions));
+
+       g_file_delete (location, NULL, NULL);
+       g_object_unref (location);
+}
+
+static void
+test_local_permissions (void)
+{
+       test_permissions (DEFAULT_LOCAL_URI, 0600);
+       test_permissions (DEFAULT_LOCAL_URI, 0660);
+       test_permissions (DEFAULT_LOCAL_URI, 0666);
+       test_permissions (DEFAULT_LOCAL_URI, 0760);
+}
+#endif
+
+static void
+test_local_unowned_directory (void)
+{
+       test_saver (UNOWNED_LOCAL_URI,
+                   DEFAULT_CONTENT,
+                   DEFAULT_CONTENT_RESULT,
+                   GTK_SOURCE_NEWLINE_TYPE_LF,
+                   NULL,
+                   NULL);
+}
+
+static void
+test_remote_unowned_directory (void)
+{
+       test_saver (UNOWNED_REMOTE_URI,
+                   DEFAULT_CONTENT,
+                   DEFAULT_CONTENT_RESULT,
+                   GTK_SOURCE_NEWLINE_TYPE_LF,
+                   NULL,
+                   NULL);
+}
+
+#ifndef G_OS_WIN32
+static void
+test_remote_permissions (void)
+{
+       test_permissions (DEFAULT_REMOTE_URI, 0600);
+       test_permissions (DEFAULT_REMOTE_URI, 0660);
+       test_permissions (DEFAULT_REMOTE_URI, 0666);
+       test_permissions (DEFAULT_REMOTE_URI, 0760);
+}
+
+static void
+test_unowned_group_permissions (SaverTestData *data)
+{
+       GError *error = NULL;
+       const gchar *group;
+       guint32 mode;
+
+       GFileInfo *info = g_file_query_info (data->location,
+                                            G_FILE_ATTRIBUTE_OWNER_GROUP ","
+                                            G_FILE_ATTRIBUTE_UNIX_MODE,
+                                            G_FILE_QUERY_INFO_NONE,
+                                            NULL,
+                                            &error);
+
+       g_assert_no_error (error);
+
+       group = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_GROUP);
+       g_assert_cmpstr (group, ==, "root");
+
+       mode = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE);
+
+       g_assert_cmpint (mode & ACCESSPERMS, ==, 0660);
+
+       g_object_unref (info);
+}
+
+static void
+test_unowned_group (const gchar *uri)
+{
+       test_saver (uri,
+                   DEFAULT_CONTENT,
+                   DEFAULT_CONTENT_RESULT,
+                   GTK_SOURCE_NEWLINE_TYPE_LF,
+                   test_unowned_group_permissions,
+                   NULL);
+}
+
+static void
+test_local_unowned_group (void)
+{
+       test_unowned_group (UNOWNED_GROUP_LOCAL_URI);
+}
+
+#if 0
+static void
+test_remote_unowned_group (void)
+{
+       test_unowned_group (UNOWNED_GROUP_REMOTE_URI);
+}
+#endif
+
+#endif
+
+static gboolean
+check_unowned_directory (void)
+{
+       GFile *unowned = g_file_new_for_path (UNOWNED_LOCAL_DIRECTORY);
+       GFile *unowned_file;
+       GFileInfo *info;
+       GError *error = NULL;
+
+       g_printf ("*** Checking for unowned directory test... ");
+
+       info = g_file_query_info (unowned,
+                                 G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
+                                 G_FILE_QUERY_INFO_NONE,
+                                 NULL,
+                                 &error);
+
+       if (error)
+       {
+               g_object_unref (unowned);
+               g_printf ("NO: directory does not exist\n");
+
+               g_error_free (error);
+               return FALSE;
+       }
+
+       if (g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
+       {
+               g_object_unref (unowned);
+
+               g_printf ("NO: directory is writable\n");
+               g_object_unref (info);
+               return FALSE;
+       }
+
+       g_object_unref (info);
+       g_object_unref (unowned);
+
+       unowned_file = g_file_new_for_commandline_arg (UNOWNED_LOCAL_URI);
+
+       info = g_file_query_info (unowned_file,
+                                 G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
+                                 G_FILE_QUERY_INFO_NONE,
+                                 NULL,
+                                 &error);
+
+       if (error)
+       {
+               g_object_unref (unowned_file);
+               g_error_free (error);
+
+               g_printf ("NO: file does not exist\n");
+               return FALSE;
+       }
+
+       if (!g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
+       {
+               g_object_unref (unowned_file);
+
+               g_printf ("NO: file is not writable\n");
+               g_object_unref (info);
+               return FALSE;
+       }
+
+       g_object_unref (info);
+       g_object_unref (unowned_file);
+
+       g_printf ("YES\n");
+       return TRUE;
+}
+
+static gboolean
+check_unowned_group (void)
+{
+       GFile *unowned = g_file_new_for_path (UNOWNED_GROUP_LOCAL_URI);
+       GFileInfo *info;
+       GError *error = NULL;
+
+       g_printf ("*** Checking for unowned group test... ");
+
+       info = g_file_query_info (unowned,
+                                 G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE ","
+                                 G_FILE_ATTRIBUTE_OWNER_GROUP ","
+                                 G_FILE_ATTRIBUTE_UNIX_MODE,
+                                 G_FILE_QUERY_INFO_NONE,
+                                 NULL,
+                                 &error);
+
+       if (error)
+       {
+               g_object_unref (unowned);
+               g_printf ("NO: file does not exist\n");
+
+               g_error_free (error);
+               return FALSE;
+       }
+
+       if (!g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
+       {
+               g_object_unref (unowned);
+
+               g_printf ("NO: file is not writable\n");
+               g_object_unref (info);
+               return FALSE;
+       }
+
+       if (g_strcmp0 (g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_GROUP),
+                      "root") != 0)
+       {
+               g_object_unref (unowned);
+
+               g_printf ("NO: group is not root (%s)\n", g_file_info_get_attribute_string (info, 
G_FILE_ATTRIBUTE_OWNER_GROUP));
+               g_object_unref (info);
+               return FALSE;
+       }
+
+#ifndef G_OS_WIN32
+       if ((g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE) & ACCESSPERMS) != 0660)
+       {
+               g_object_unref (unowned);
+
+               g_printf ("NO: file has wrong permissions\n");
+               g_object_unref (info);
+               return FALSE;
+       }
+#endif
+
+       g_object_unref (info);
+       g_object_unref (unowned);
+
+       g_printf ("YES\n");
+       return TRUE;
+}
+
+static void
+all_tests (void)
+{
+       gboolean have_unowned;
+       gboolean have_unowned_group;
+
+       g_printf ("\n***\n");
+       have_unowned = check_unowned_directory ();
+       have_unowned_group = check_unowned_group ();
+       g_printf ("***\n\n");
+
+       g_test_trap_subprocess ("/file-saver/subprocess/local",
+                               0,
+                               G_TEST_SUBPROCESS_INHERIT_STDERR);
+       g_test_trap_assert_passed ();
+
+       g_test_trap_subprocess ("/file-saver/subprocess/local-new-line",
+                               0,
+                               G_TEST_SUBPROCESS_INHERIT_STDERR);
+       g_test_trap_assert_passed ();
+
+       if (have_unowned)
+       {
+               g_test_trap_subprocess ("/file-saver/subprocess/local-unowned-directory",
+                                       0,
+                                       G_TEST_SUBPROCESS_INHERIT_STDERR);
+               g_test_trap_assert_passed ();
+       }
+
+       g_test_trap_subprocess ("/file-saver/subprocess/remote",
+                               0,
+                               G_TEST_SUBPROCESS_INHERIT_STDERR);
+       g_test_trap_assert_passed ();
+
+       g_test_trap_subprocess ("/file-saver/subprocess/remote-new-line",
+                               0,
+                               G_TEST_SUBPROCESS_INHERIT_STDERR);
+       g_test_trap_assert_passed ();
+
+       if (have_unowned)
+       {
+               g_test_trap_subprocess ("/file-saver/subprocess/remote-unowned-directory",
+                                       0,
+                                       G_TEST_SUBPROCESS_INHERIT_STDERR);
+               g_test_trap_assert_passed ();
+       }
+
+#if 0
+       if (have_unowned_group)
+       {
+               g_test_trap_subprocess ("/file-saver/subprocess/remote-unowned-group",
+                                       0,
+                                       G_TEST_SUBPROCESS_INHERIT_STDERR);
+               g_test_trap_assert_passed ();
+       }
+#endif
+
+#ifndef G_OS_WIN32
+       g_test_trap_subprocess ("/file-saver/subprocess/local-permissions",
+                               0,
+                               G_TEST_SUBPROCESS_INHERIT_STDERR);
+       g_test_trap_assert_passed ();
+
+       if (have_unowned_group)
+       {
+               g_test_trap_subprocess ("/file-saver/subprocess/local-unowned-group",
+                                       0,
+                                       G_TEST_SUBPROCESS_INHERIT_STDERR);
+               g_test_trap_assert_passed ();
+       }
+
+       g_test_trap_subprocess ("/file-saver/subprocess/remote-permissions",
+                               0,
+                               G_TEST_SUBPROCESS_INHERIT_STDERR);
+       g_test_trap_assert_passed ();
+#endif
+}
+
+gint
+main (gint   argc,
+      gchar *argv[])
+{
+       gtk_test_init (&argc, &argv);
+
+       g_test_add_func ("/file-saver", all_tests);
+
+       g_test_add_func ("/file-saver/subprocess/local", test_local);
+       g_test_add_func ("/file-saver/subprocess/local-new-line", test_local_newline);
+       g_test_add_func ("/file-saver/subprocess/local-unowned-directory", test_local_unowned_directory);
+       g_test_add_func ("/file-saver/subprocess/remote", test_remote);
+       g_test_add_func ("/file-saver/subprocess/remote-new-line", test_remote_newline);
+       g_test_add_func ("/file-saver/subprocess/remote-unowned-directory", test_remote_unowned_directory);
+
+       /* FIXME: there is a bug in gvfs sftp which doesn't pass this test */
+       /* g_test_add_func ("/file-saver/subprocess/remote-unowned-group", test_remote_unowned_group); */
+
+#ifndef G_OS_WIN32
+       g_test_add_func ("/file-saver/subprocess/local-permissions", test_local_permissions);
+       g_test_add_func ("/file-saver/subprocess/local-unowned-group", test_local_unowned_group);
+       g_test_add_func ("/file-saver/subprocess/remote-permissions", test_remote_permissions);
+#endif
+
+       return g_test_run ();
+}



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