[gtksourceview/wip/loader-saver: 7/31] File loading and saving
- From: Sébastien Wilmet <swilmet src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtksourceview/wip/loader-saver: 7/31] File loading and saving
- Date: Sun, 6 Jul 2014 13:21:53 +0000 (UTC)
commit b7fc29eff40e17d09fe2054ba19df4f6beaea8c8
Author: Sébastien Wilmet <swilmet gnome org>
Date: Fri Dec 6 19:05:33 2013 +0100
File loading and saving
New public classes: GtkSourceEncoding, GtkSourceFileLoader and
GtkSourceFileSaver.
There are also new properties in GtkSourceBuffer.
https://bugzilla.gnome.org/show_bug.cgi?id=721016
docs/reference/Makefile.am | 2 +
docs/reference/gtksourceview-3.0-sections.txt | 93 ++
docs/reference/gtksourceview-docs.xml | 7 +
gtksourceview/Makefile.am | 30 +-
gtksourceview/gtksource.h | 3 +
gtksourceview/gtksourcebuffer-private.h | 21 +
gtksourceview/gtksourcebuffer.c | 351 +++++++-
gtksourceview/gtksourcebuffer.h | 60 ++
gtksourceview/gtksourcebufferinputstream.c | 498 ++++++++++
gtksourceview/gtksourcebufferinputstream.h | 71 ++
gtksourceview/gtksourcebufferoutputstream.c | 1113 ++++++++++++++++++++++
gtksourceview/gtksourcebufferoutputstream.h | 76 ++
gtksourceview/gtksourceencoding.c | 502 ++++++++++
gtksourceview/gtksourceencoding.h | 59 ++
gtksourceview/gtksourcefileloader.c | 1037 +++++++++++++++++++++
gtksourceview/gtksourcefileloader.h | 123 +++
gtksourceview/gtksourcefilesaver.c | 1238 +++++++++++++++++++++++++
gtksourceview/gtksourcefilesaver.h | 113 +++
gtksourceview/gtksourcetypes-private.h | 2 +
gtksourceview/gtksourcetypes.h | 3 +
po/POTFILES.in | 5 +
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 | 759 +++++++++++++++
27 files changed, 7028 insertions(+), 11 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..e7c2952 100644
--- a/docs/reference/gtksourceview-3.0-sections.txt
+++ b/docs/reference/gtksourceview-3.0-sections.txt
@@ -6,6 +6,10 @@
GtkSourceBuffer
GtkSourceBracketMatchType
GtkSourceChangeCaseType
+GtkSourceNewlineType
+GTK_SOURCE_NEWLINE_TYPE_DEFAULT
+GtkSourceCompressionType
+GtkSourceMountOperationFactory
gtk_source_buffer_new
gtk_source_buffer_new_with_language
gtk_source_buffer_set_highlight_syntax
@@ -39,6 +43,13 @@ 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_get_file
+gtk_source_buffer_get_encoding
+gtk_source_buffer_get_newline_type
+gtk_source_buffer_get_compression_type
+gtk_source_buffer_set_implicit_trailing_newline
+gtk_source_buffer_get_implicit_trailing_newline
<SUBSECTION Standard>
GtkSourceBufferClass
GTK_SOURCE_IS_BUFFER
@@ -207,6 +218,88 @@ gtk_source_completion_words_get_type
</SECTION>
<SECTION>
+<FILE>encoding</FILE>
+<TITLE>GtkSourceEncoding</TITLE>
+GtkSourceEncoding
+<SUBSECTION>
+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_get_utf8
+gtk_source_encoding_get_current
+gtk_source_encoding_copy
+gtk_source_encoding_free
+<SUBSECTION Standard>
+GTK_SOURCE_TYPE_ENCODING
+gtk_source_encoding_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_input_stream
+gtk_source_file_loader_set_mount_operation_factory
+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_new
+gtk_source_file_saver_get_buffer
+gtk_source_file_saver_get_file
+gtk_source_file_saver_set_main_file
+gtk_source_file_saver_get_main_file
+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_create_backup
+gtk_source_file_saver_get_create_backup
+gtk_source_file_saver_set_mount_operation_factory
+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
+</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..6de50f4 100644
--- a/docs/reference/gtksourceview-docs.xml
+++ b/docs/reference/gtksourceview-docs.xml
@@ -24,6 +24,9 @@
<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/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 +83,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 0d98a71..4a5476d 100644
--- a/gtksourceview/Makefile.am
+++ b/gtksourceview/Makefile.am
@@ -30,6 +30,9 @@ libgtksourceview_headers = \
gtksourcecompletionitem.h \
gtksourcecompletionproposal.h \
gtksourcecompletionprovider.h \
+ gtksourceencoding.h \
+ gtksourcefileloader.h \
+ gtksourcefilesaver.h \
gtksourcegutter.h \
gtksourcegutterrenderer.h \
gtksourcegutterrendererpixbuf.h \
@@ -51,6 +54,8 @@ libgtksourceview_headers = \
libgtksourceview_private_headers = \
gtksourcebuffer-private.h \
+ gtksourcebufferinputstream.h \
+ gtksourcebufferoutputstream.h \
gtksourcecompletioncontainer.h \
gtksourcecompletionmodel.h \
gtksourcecompletion-private.h \
@@ -72,6 +77,8 @@ libgtksourceview_private_headers = \
gtktextregion.h
libgtksourceview_private_c_files = \
+ gtksourcebufferinputstream.c \
+ gtksourcebufferoutputstream.c \
gtksourcecompletioncontainer.c \
gtksourcecompletionmodel.c \
gtksourcecontextengine.c \
@@ -97,6 +104,9 @@ libgtksourceview_c_files = \
gtksourcecompletionitem.c \
gtksourcecompletionproposal.c \
gtksourcecompletionprovider.c \
+ gtksourceencoding.c \
+ gtksourcefileloader.c \
+ gtksourcefilesaver.c \
gtksourcegutter.c \
gtksourcegutterrenderer.c \
gtksourcegutterrendererpixbuf.c \
@@ -119,25 +129,25 @@ noinst_LTLIBRARIES = libgtksourceview-private.la
libgtksourceview_private_la_SOURCES = \
$(libgtksourceview_private_c_files) \
- $(libgtksourceview_private_headers)
+ $(libgtksourceview_private_headers) \
+ $(libgtksourceview_c_files) \
+ $(libgtksourceview_headers)
+
+# do not distribute generated files
+nodist_libgtksourceview_private_la_SOURCES = \
+ $(BUILT_SOURCES)
libgtksourceview_private_la_CFLAGS = \
$(CODE_COVERAGE_CFLAGS)
-libgtksourceview_private_la_LDFLAGS = \
- -no-undefined \
+libgtksourceview_private_la_LDFLAGS = \
+ -no-undefined \
$(CODE_COVERAGE_LDFLAGS)
# The real library
lib_LTLIBRARIES = libgtksourceview-3.0.la
-libgtksourceview_3_0_la_SOURCES = \
- $(libgtksourceview_c_files) \
- $(libgtksourceview_headers)
-
-# do not distribute generated files
-nodist_libgtksourceview_3_0_la_SOURCES =\
- $(BUILT_SOURCES)
+libgtksourceview_3_0_la_SOURCES =
libgtksourceview_3_0_la_LIBADD = \
libgtksourceview-private.la \
diff --git a/gtksourceview/gtksource.h b/gtksourceview/gtksource.h
index 1c7bd79..89644d1 100644
--- a/gtksourceview/gtksource.h
+++ b/gtksourceview/gtksource.h
@@ -28,6 +28,9 @@
#include <gtksourceview/gtksourcecompletionitem.h>
#include <gtksourceview/gtksourcecompletionproposal.h>
#include <gtksourceview/gtksourcecompletionprovider.h>
+#include <gtksourceview/gtksourceencoding.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..96560d0 100644
--- a/gtksourceview/gtksourcebuffer-private.h
+++ b/gtksourceview/gtksourcebuffer-private.h
@@ -50,6 +50,27 @@ G_GNUC_INTERNAL
void _gtk_source_buffer_add_search_context (GtkSourceBuffer *buffer,
GtkSourceSearchContext
*search_context);
+G_GNUC_INTERNAL
+void _gtk_source_buffer_set_as_invalid_character (GtkSourceBuffer *buffer,
+ GtkTextIter *start,
+ GtkTextIter *end);
+
+G_GNUC_INTERNAL
+void _gtk_source_buffer_set_file (GtkSourceBuffer *buffer,
+ GFile *file);
+
+G_GNUC_INTERNAL
+void _gtk_source_buffer_set_encoding (GtkSourceBuffer *buffer,
+ const GtkSourceEncoding *encoding);
+
+G_GNUC_INTERNAL
+void _gtk_source_buffer_set_newline_type (GtkSourceBuffer *buffer,
+ GtkSourceNewlineType
newline_type);
+
+G_GNUC_INTERNAL
+void _gtk_source_buffer_set_compression_type (GtkSourceBuffer *buffer,
+ GtkSourceCompressionType
compression_type);
+
G_END_DECLS
#endif /* __GTK_SOURCE_BUFFER_PRIVATE_H__ */
diff --git a/gtksourceview/gtksourcebuffer.c b/gtksourceview/gtksourcebuffer.c
index d9b5886..62c5cd5 100644
--- a/gtksourceview/gtksourcebuffer.c
+++ b/gtksourceview/gtksourcebuffer.c
@@ -42,6 +42,7 @@
#include "gtksourcemark.h"
#include "gtksourcemarkssequence.h"
#include "gtksourcesearchcontext.h"
+#include "gtksourceencoding.h"
#include "gtksourceview-i18n.h"
#include "gtksourceview-marshal.h"
#include "gtksourceview-typebuiltins.h"
@@ -140,7 +141,12 @@ enum {
PROP_MAX_UNDO_LEVELS,
PROP_LANGUAGE,
PROP_STYLE_SCHEME,
- PROP_UNDO_MANAGER
+ PROP_UNDO_MANAGER,
+ PROP_FILE,
+ PROP_ENCODING,
+ PROP_NEWLINE_TYPE,
+ PROP_COMPRESSION_TYPE,
+ PROP_IMPLICIT_TRAILING_NEWLINE
};
struct _GtkSourceBufferPrivate
@@ -164,10 +170,18 @@ struct _GtkSourceBufferPrivate
GList *search_contexts;
+ GtkTextTag *invalid_char_tag;
+
+ GFile *file;
+ const GtkSourceEncoding *encoding;
+ GtkSourceNewlineType newline_type;
+ GtkSourceCompressionType compression_type;
+
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 +371,85 @@ gtk_source_buffer_class_init (GtkSourceBufferClass *klass)
GTK_SOURCE_TYPE_UNDO_MANAGER,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+ /**
+ * GtkSourceBuffer:file:
+ *
+ * The associated #GFile.
+ *
+ * Since: 3.14
+ */
+ g_object_class_install_property (object_class,
+ PROP_FILE,
+ g_param_spec_object ("file",
+ _("File"),
+ "",
+ G_TYPE_FILE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GtkSourceBuffer:encoding:
+ *
+ * The #GtkSourceBuffer:file's encoding. Note that the #GtkSourceBuffer
+ * always has a UTF-8 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));
+
+ /**
+ * GtkSourceBuffer:newline-type:
+ *
+ * The type of line ending.
+ *
+ * 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));
+
+ /**
+ * GtkSourceBuffer:compression-type:
+ *
+ * The #GtkSourceBuffer:file's 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));
+
+ /**
+ * 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;
@@ -533,6 +626,8 @@ gtk_source_buffer_init (GtkSourceBuffer *buffer)
{
g_object_ref (priv->style_scheme);
}
+
+ priv->encoding = gtk_source_encoding_get_utf8 ();
}
static void
@@ -627,6 +722,10 @@ gtk_source_buffer_set_property (GObject *object,
g_value_get_object (value));
break;
+ case PROP_IMPLICIT_TRAILING_NEWLINE:
+ source_buffer->priv->implicit_trailing_newline = g_value_get_boolean (value);
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -682,6 +781,26 @@ gtk_source_buffer_get_property (GObject *object,
g_value_set_object (value, source_buffer->priv->undo_manager);
break;
+ case PROP_FILE:
+ g_value_set_object (value, source_buffer->priv->file);
+ break;
+
+ case PROP_ENCODING:
+ g_value_set_boxed (value, source_buffer->priv->encoding);
+ break;
+
+ case PROP_NEWLINE_TYPE:
+ g_value_set_enum (value, source_buffer->priv->newline_type);
+ break;
+
+ case PROP_COMPRESSION_TYPE:
+ g_value_set_enum (value, source_buffer->priv->compression_type);
+ 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 +2602,233 @@ _gtk_source_buffer_add_search_context (GtkSourceBuffer *buffer,
(GWeakNotify)search_context_weak_notify_cb,
buffer);
}
+
+static void
+sync_invalid_char_tag (GtkSourceBuffer *buffer,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ GtkSourceStyle *style = NULL;
+
+ if (buffer->priv->style_scheme != NULL)
+ {
+ style = gtk_source_style_scheme_get_style (buffer->priv->style_scheme, "def:error");
+ }
+
+ _gtk_source_style_apply (style, buffer->priv->invalid_char_tag);
+}
+
+static void
+text_tag_set_highest_priority (GtkTextTag *tag,
+ GtkTextBuffer *buffer)
+{
+ GtkTextTagTable *table;
+ gint n;
+
+ table = gtk_text_buffer_get_tag_table (buffer);
+ n = gtk_text_tag_table_get_size (table);
+ gtk_text_tag_set_priority (tag, n - 1);
+}
+
+void
+_gtk_source_buffer_set_as_invalid_character (GtkSourceBuffer *buffer,
+ GtkTextIter *start,
+ GtkTextIter *end)
+{
+ if (buffer->priv->invalid_char_tag == NULL)
+ {
+ buffer->priv->invalid_char_tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer),
+ "invalid-char-style",
+ NULL);
+
+ sync_invalid_char_tag (buffer, NULL, NULL);
+
+ g_signal_connect (buffer,
+ "notify::style-scheme",
+ G_CALLBACK (sync_invalid_char_tag),
+ NULL);
+ }
+
+ /* Make sure the 'error' tag has the priority over
+ * syntax highlighting tags.
+ */
+ text_tag_set_highest_priority (buffer->priv->invalid_char_tag,
+ GTK_TEXT_BUFFER (buffer));
+
+ gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer),
+ buffer->priv->invalid_char_tag,
+ start,
+ end);
+}
+
+void
+_gtk_source_buffer_set_file (GtkSourceBuffer *buffer,
+ GFile *file)
+{
+ g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
+
+ if (buffer->priv->file != file)
+ {
+ g_clear_object (&buffer->priv->file);
+ buffer->priv->file = g_object_ref (file);
+ g_object_notify (G_OBJECT (buffer), "file");
+ }
+}
+
+/**
+ * gtk_source_buffer_get_file:
+ * @buffer: a #GtkSourceBuffer.
+ *
+ * Returns: (transfer none): the associated #GFile.
+ * Since: 3.14
+ */
+GFile *
+gtk_source_buffer_get_file (GtkSourceBuffer *buffer)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL);
+
+ return buffer->priv->file;
+}
+
+void
+_gtk_source_buffer_set_encoding (GtkSourceBuffer *buffer,
+ const GtkSourceEncoding *encoding)
+{
+ g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
+
+ if (encoding == NULL)
+ {
+ encoding = gtk_source_encoding_get_utf8 ();
+ }
+
+ if (buffer->priv->encoding != encoding)
+ {
+ buffer->priv->encoding = encoding;
+ g_object_notify (G_OBJECT (buffer), "encoding");
+ }
+}
+
+/**
+ * gtk_source_buffer_get_encoding:
+ * @buffer: a #GtkSourceBuffer.
+ *
+ * Returns: the #GtkSourceBuffer:file's encoding. Note that the @buffer always
+ * has a UTF-8 encoding.
+ * Since: 3.14
+ */
+const GtkSourceEncoding *
+gtk_source_buffer_get_encoding (GtkSourceBuffer *buffer)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL);
+
+ return buffer->priv->encoding;
+}
+
+void
+_gtk_source_buffer_set_newline_type (GtkSourceBuffer *buffer,
+ GtkSourceNewlineType newline_type)
+{
+ g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
+
+ if (buffer->priv->newline_type != newline_type)
+ {
+ buffer->priv->newline_type = newline_type;
+ g_object_notify (G_OBJECT (buffer), "newline-type");
+ }
+}
+
+/**
+ * gtk_source_buffer_get_newline_type:
+ * @buffer: a #GtkSourceBuffer.
+ *
+ * Returns: the #GtkSourceBuffer:file's newline type.
+ * Since: 3.14
+ */
+GtkSourceNewlineType
+gtk_source_buffer_get_newline_type (GtkSourceBuffer *buffer)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), GTK_SOURCE_NEWLINE_TYPE_DEFAULT);
+
+ return buffer->priv->newline_type;
+}
+
+void
+_gtk_source_buffer_set_compression_type (GtkSourceBuffer *buffer,
+ GtkSourceCompressionType compression_type)
+{
+ g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
+
+ if (buffer->priv->compression_type != compression_type)
+ {
+ buffer->priv->compression_type = compression_type;
+ g_object_notify (G_OBJECT (buffer), "compression-type");
+ }
+}
+
+/**
+ * gtk_source_buffer_get_compression_type:
+ * @buffer: a #GtkSourceBuffer.
+ *
+ * Returns: the #GtkSourceBuffer:file's compression type.
+ * Since: 3.14
+ */
+GtkSourceCompressionType
+gtk_source_buffer_get_compression_type (GtkSourceBuffer *buffer)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), GTK_SOURCE_COMPRESSION_TYPE_NONE);
+
+ return buffer->priv->compression_type;
+}
+
+/**
+ * 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..0b5e8ce 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
@@ -73,6 +74,52 @@ typedef enum
GTK_SOURCE_CHANGE_CASE_TITLE
} GtkSourceChangeCaseType;
+/**
+ * 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;
+
+#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: save file in plain text.
+ * @GTK_SOURCE_COMPRESSION_TYPE_GZIP: save file using gzip compression.
+ *
+ * Since: 3.14
+ */
+typedef enum
+{
+ GTK_SOURCE_COMPRESSION_TYPE_NONE,
+ GTK_SOURCE_COMPRESSION_TYPE_GZIP
+} GtkSourceCompressionType;
+
+/**
+ * GtkSourceMountOperationFactory:
+ * @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)(gpointer userdata);
+
struct _GtkSourceBuffer
{
GtkTextBuffer parent_instance;
@@ -202,6 +249,19 @@ GtkSourceUndoManager *gtk_source_buffer_get_undo_manager
(GtkSourceBuffer *buf
void gtk_source_buffer_set_undo_manager (GtkSourceBuffer
*buffer,
GtkSourceUndoManager
*manager);
+GFile *gtk_source_buffer_get_file (GtkSourceBuffer
*buffer);
+
+const GtkSourceEncoding *gtk_source_buffer_get_encoding (GtkSourceBuffer
*buffer);
+
+GtkSourceNewlineType gtk_source_buffer_get_newline_type (GtkSourceBuffer
*buffer);
+
+GtkSourceCompressionType gtk_source_buffer_get_compression_type (GtkSourceBuffer
*buffer);
+
+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..8a7fcbf
--- /dev/null
+++ b/gtksourceview/gtksourcebufferinputstream.c
@@ -0,0 +1,498 @@
+/* -*- 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;
+
+ 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;
+ }
+
+ /* 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)
+ {
+ 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 #GInputStream to read @buffer
+ */
+GInputStream *
+_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);
+
+ 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)
+ {
+ 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..b80c7c4
--- /dev/null
+++ b/gtksourceview/gtksourcebufferinputstream.h
@@ -0,0 +1,71 @@
+/* -*- 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
+GInputStream *_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..b4e4f77
--- /dev/null
+++ b/gtksourceview/gtksourcebufferoutputstream.c
@@ -0,0 +1,1113 @@
+/* -*- 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 (©);
+
+ if (g_unichar_break_type (c) == G_UNICODE_BREAK_CARRIAGE_RETURN)
+ {
+ if (gtk_text_iter_forward_char (©) &&
+ g_unichar_break_type (gtk_text_iter_get_char (©)) == G_UNICODE_BREAK_LINE_FEED)
+ {
+ res = GTK_SOURCE_NEWLINE_TYPE_CR_LF;
+ }
+ else
+ {
+ res = GTK_SOURCE_NEWLINE_TYPE_CR;
+ }
+ }
+ else
+ {
+ res = GTK_SOURCE_NEWLINE_TYPE_LF;
+ }
+
+ return res;
+}
+
+GOutputStream *
+gtk_source_buffer_output_stream_new (GtkSourceBuffer *buffer,
+ GSList *candidate_encodings,
+ gboolean 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 G_OUTPUT_STREAM (stream);
+}
+
+GtkSourceNewlineType
+gtk_source_buffer_output_stream_detect_newline_type (GtkSourceBufferOutputStream *stream)
+{
+ GtkSourceNewlineType type;
+ GtkTextIter iter;
+
+ g_return_val_if_fail (GTK_SOURCE_IS_BUFFER_OUTPUT_STREAM (stream),
+ GTK_SOURCE_NEWLINE_TYPE_DEFAULT);
+
+ type = GTK_SOURCE_NEWLINE_TYPE_DEFAULT;
+
+ gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (stream->priv->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)
+ {
+ 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 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;
+
+ 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;
+
+ 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->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;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ {
+ return -1;
+ }
+
+ ostream = GTK_SOURCE_BUFFER_OUTPUT_STREAM (stream);
+
+ if (!ostream->priv->is_initialized)
+ {
+ ostream->priv->charset_conv = guess_encoding (ostream, buffer, count);
+
+ /* If we still have the previous case is that we didn't guess
+ anything */
+ if (ostream->priv->charset_conv == NULL &&
+ !ostream->priv->is_utf8)
+ {
+ g_set_error_literal (error, GTK_SOURCE_FILE_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)
+ {
+ 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..62b2f5c
--- /dev/null
+++ b/gtksourceview/gtksourcebufferoutputstream.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* gtksourcebufferoutputstream.h
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2010 - Ignacio Casal Quinteiro
+ * Copyright (C) 2014 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GTK_SOURCE_BUFFER_OUTPUT_STREAM_H__
+#define __GTK_SOURCE_BUFFER_OUTPUT_STREAM_H__
+
+#include <gtk/gtk.h>
+#include "gtksourcetypes.h"
+#include "gtksourcetypes-private.h"
+#include "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
+GOutputStream *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..0be5ff6
--- /dev/null
+++ b/gtksourceview/gtksourceencoding.c
@@ -0,0 +1,502 @@
+/* -*- 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"
+
+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;
+}
+
+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;
+}
+
+const GtkSourceEncoding *
+gtk_source_encoding_get_from_index (gint idx)
+{
+ g_return_val_if_fail (idx >= 0, NULL);
+
+ if (idx >= GTK_SOURCE_ENCODING_LAST)
+ {
+ return NULL;
+ }
+
+ return &encodings[idx];
+}
+
+const GtkSourceEncoding *
+gtk_source_encoding_get_utf8 (void)
+{
+ return &utf8_encoding;
+}
+
+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;
+}
+
+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);
+ }
+}
+
+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;
+}
+
+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);
+}
+
+/* These are to make language bindings happy. Since Encodings are
+ * const, copy() just returns the same pointer and free() does
+ * nothing. */
+
+GtkSourceEncoding *
+gtk_source_encoding_copy (const GtkSourceEncoding *enc)
+{
+ g_return_val_if_fail (enc != NULL, NULL);
+
+ return (GtkSourceEncoding *) enc;
+}
+
+void
+gtk_source_encoding_free (GtkSourceEncoding *enc)
+{
+ g_return_if_fail (enc != NULL);
+}
+
+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);
+}
diff --git a/gtksourceview/gtksourceencoding.h b/gtksourceview/gtksourceencoding.h
new file mode 100644
index 0000000..44c4611
--- /dev/null
+++ b/gtksourceview/gtksourceencoding.h
@@ -0,0 +1,59 @@
+/* -*- 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);
+
+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);
+
+G_END_DECLS
+
+#endif /* __GTK_SOURCE_ENCODING_H__ */
diff --git a/gtksourceview/gtksourcefileloader.c b/gtksourceview/gtksourcefileloader.c
new file mode 100644
index 0000000..d4426ca
--- /dev/null
+++ b/gtksourceview/gtksourcefileloader.c
@@ -0,0 +1,1037 @@
+/* -*- 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 <gio/gio.h>
+#include "gtksourcefileloader.h"
+#include "gtksourcebufferoutputstream.h"
+#include "gtksourceencoding.h"
+#include "gtksourcebuffer-private.h"
+#include "gtksourceview-typebuiltins.h"
+#include "gtksourceview-i18n.h"
+
+/**
+ * SECTION:fileloader
+ * @Short_description: Load a file into a GtkSourceBuffer
+ * @Title: GtkSourceFileLoader
+ * @See_also: #GtkSourceFileSaver, #GtkSourceBuffer
+ *
+ * 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_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 reference to the source_buffer. A subclass of GtkSourceBuffer
+ * can have the ownership of the FileLoader. And a FileLoader can be
+ * used several times, so the object can be kept around until the
+ * subclass of GtkSourceBuffer is disposed.
+ * The GtkSourceBufferOutputStream has a strong reference to the
+ * source_buffer, so the buffer will not be finalized when the file is
+ * being loaded. So there can be a temporary reference cycle.
+ */
+ GtkSourceBuffer *source_buffer;
+
+ GFile *file;
+
+ /* 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;
+
+ GtkSourceMountOperationFactory mount_operation_factory;
+ gpointer mount_operation_userdata;
+
+ GTask *task;
+
+ goffset total_bytes_read;
+ goffset total_size;
+ GFileProgressCallback progress_cb;
+ gpointer progress_cb_data;
+
+ /* FIXME is it not better to allocate this field separately? I think the
+ * private struct is allocated with GSlice by GObject, and GSlice is
+ * better to use only for small struct, no?
+ */
+ gchar chunk_buffer[READ_CHUNK_SIZE];
+ gssize chunk_bytes_read;
+
+ GInputStream *input_stream;
+ GOutputStream *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_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_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);
+}
+
+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;
+ }
+
+ g_clear_object (&loader->priv->file);
+ 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->source_buffer != NULL)
+ {
+ const GtkSourceEncoding *encoding;
+
+ encoding = gtk_source_buffer_get_encoding (loader->priv->source_buffer);
+
+ 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->get_property = gtk_source_file_loader_get_property;
+ object_class->set_property = gtk_source_file_loader_set_property;
+ object_class->constructed = gtk_source_file_loader_constructed;
+
+ /**
+ * GtkSourceFileLoader:buffer:
+ *
+ * The output #GtkSourceBuffer.
+ *
+ * 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 #GFile to load.
+ *
+ * Since: 3.14
+ */
+ g_object_class_install_property (object_class, PROP_FILE,
+ g_param_spec_object ("file",
+ _("File"),
+ "",
+ G_TYPE_FILE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GtkSourceFileLoader:input-stream:
+ *
+ * The #GInputStream to load. Useful for reading e.g. stdin. If this
+ * property is set, it has a higher priority than
+ * the #GtkSourceFileLoader:file property.
+ *
+ * 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;
+ GtkSourceBufferOutputStream *output_stream;
+
+ 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 (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.
+ */
+ output_stream = GTK_SOURCE_BUFFER_OUTPUT_STREAM (loader->priv->output_stream);
+ if (gtk_source_buffer_output_stream_get_num_fallbacks (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 (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)
+ {
+ GtkSourceBufferOutputStream *output_stream;
+
+ output_stream = GTK_SOURCE_BUFFER_OUTPUT_STREAM (loader->priv->output_stream);
+
+ /* Flush the stream to ensure proper line ending detection. */
+ g_output_stream_flush (loader->priv->output_stream, NULL, NULL);
+
+ loader->priv->auto_detected_encoding =
+ gtk_source_buffer_output_stream_get_guessed (output_stream);
+
+ loader->priv->auto_detected_newline_type =
+ gtk_source_buffer_output_stream_detect_newline_type (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 GMountOperation *
+create_mount_operation (GtkSourceFileLoader *loader)
+{
+ return loader->priv->mount_operation_factory != NULL ?
+ loader->priv->mount_operation_factory (loader->priv->mount_operation_userdata) :
+ g_mount_operation_new ();
+}
+
+static void
+recover_not_mounted (GtkSourceFileLoader *loader)
+{
+ GMountOperation *mount_operation = create_mount_operation (loader);
+
+ DEBUG ({
+ g_print ("%s\n", G_STRFUNC);
+ });
+
+ loader->priv->tried_mount = TRUE;
+
+ g_file_mount_enclosing_volume (loader->priv->file,
+ 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->file,
+ 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 output #GtkSourceBuffer.
+ * @file: the #GFile to load.
+ *
+ * Returns: a new #GtkSourceFileLoader object.
+ * Since: 3.14
+ */
+GtkSourceFileLoader *
+gtk_source_file_loader_new (GtkSourceBuffer *buffer,
+ GFile *file)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL);
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+
+ return g_object_new (GTK_SOURCE_TYPE_FILE_LOADER,
+ "buffer", buffer,
+ "file", file,
+ NULL);
+}
+
+/**
+ * gtk_source_file_loader_new_from_stream:
+ * @buffer: the output #GtkSourceBuffer.
+ * @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,
+ GInputStream *stream)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (stream), NULL);
+
+ return g_object_new (GTK_SOURCE_TYPE_FILE_LOADER,
+ "buffer", buffer,
+ "input-stream", stream,
+ NULL);
+}
+
+/**
+ * gtk_source_file_loader_set_candidate_encodings:
+ * @loader: a #GtkSourceFileLoader.
+ * @candidate_encodings: a %NULL-terminated 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 content 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 #GFile to load, or %NULL if an input stream is
+ * used.
+ * Since: 3.14
+ */
+GFile *
+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_input_stream:
+ * @loader: a #GtkSourceFileLoader.
+ *
+ * Returns: (transfer none): the #GInputStream to load, or %NULL if a file 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: (allow-none): optional #GCancellable object, %NULL to ignore.
+ * @progress_callback: (allow-none): 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.
+ * @callback: (scope async): a #GAsyncReadyCallback to call when the request is
+ * satisfied.
+ * @user_data: user data to pass to @callback.
+ *
+ * Loads asynchronously the #GtkSourceFileLoader:file into the
+ * #GtkSourceFileLoader:buffer. See the #GAsyncResult documentation to know how to
+ * use this function.
+ *
+ * Since: 3.14
+ */
+void
+gtk_source_file_loader_load_async (GtkSourceFileLoader *loader,
+ gint io_priority,
+ GCancellable *cancellable,
+ GFileProgressCallback progress_callback,
+ gpointer progress_callback_data,
+ 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);
+
+ 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;
+
+ DEBUG ({
+ g_print ("Start loading\n");
+ });
+
+ implicit_trailing_newline = gtk_source_buffer_get_implicit_trailing_newline
(loader->priv->source_buffer);
+
+ /* The BufferOutputStream has a strong reference to the source_buffer.
+ * We create the BufferOutputStream here so we are sure that the
+ * source_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 file has been loaded, the following #GtkSourceBuffer properties will
+ * be updated: the file, the encoding, the newline type and the compression
+ * type.
+ *
+ * Returns: whether the file was 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 buffer properties if the file has been loaded. The file
+ * 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 file has anyway be loaded, the buffer
+ * properties must be updated.
+ * With the other errors, normally the file 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->source_buffer != NULL)
+ {
+ _gtk_source_buffer_set_file (loader->priv->source_buffer,
+ loader->priv->file);
+
+ _gtk_source_buffer_set_encoding (loader->priv->source_buffer,
+ loader->priv->auto_detected_encoding);
+
+ _gtk_source_buffer_set_newline_type (loader->priv->source_buffer,
+ loader->priv->auto_detected_newline_type);
+
+ _gtk_source_buffer_set_compression_type (loader->priv->source_buffer,
+ loader->priv->auto_detected_compression_type);
+ }
+
+ 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;
+}
+
+/**
+ * gtk_source_file_loader_set_mount_operation_factory:
+ * @loader: a #GtkSourceFileLoader.
+ * @callback: a #GtkSourceMountOperationFactory to call when a #GMountOperation
+ * is needed.
+ * @user_data: the data to pass to the @callback function.
+ *
+ * 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.
+ *
+ * Since: 3.14
+ */
+void
+gtk_source_file_loader_set_mount_operation_factory (GtkSourceFileLoader *loader,
+ GtkSourceMountOperationFactory callback,
+ gpointer user_data)
+{
+ g_return_if_fail (GTK_SOURCE_IS_FILE_LOADER (loader));
+
+ loader->priv->mount_operation_factory = callback;
+ loader->priv->mount_operation_userdata = user_data;
+}
diff --git a/gtksourceview/gtksourcefileloader.h b/gtksourceview/gtksourcefileloader.h
new file mode 100644
index 0000000..e6cd1e1
--- /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>
+#include <gtksourceview/gtksourcebuffer.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 %GTK_SOURCE_FILE_LOADER_ERROR in a #GError returned
+ * from a file loading related function.
+ */
+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;
+};
+
+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,
+ GFile *file);
+
+GtkSourceFileLoader *gtk_source_file_loader_new_from_stream (GtkSourceBuffer *buffer,
+ GInputStream *stream);
+
+void gtk_source_file_loader_set_candidate_encodings
+ (GtkSourceFileLoader *loader,
+ GSList
*candidate_encodings);
+
+GtkSourceBuffer *gtk_source_file_loader_get_buffer (GtkSourceFileLoader *loader);
+
+GFile *gtk_source_file_loader_get_file (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,
+ 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);
+
+void gtk_source_file_loader_set_mount_operation_factory
+ (GtkSourceFileLoader *loader,
+ GtkSourceMountOperationFactory callback,
+ gpointer user_data);
+
+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..ed76217
--- /dev/null
+++ b/gtksourceview/gtksourcefilesaver.c
@@ -0,0 +1,1238 @@
+/* -*- 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 "gtksourcebufferinputstream.h"
+#include "gtksourceencoding.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: #GtkSourceFileLoader, #GtkSourceBuffer
+ *
+ * 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_MAIN_FILE,
+ PROP_ENCODING,
+ PROP_NEWLINE_TYPE,
+ PROP_COMPRESSION_TYPE,
+ PROP_CREATE_BACKUP
+};
+
+struct _GtkSourceFileSaverPrivate
+{
+ /* Weak reference to the source_buffer. A subclass of GtkSourceBuffer
+ * can have the ownership of the FileSaver. And a FileSaver can be used
+ * several times, so the object can be kept around until the subclass of
+ * GtkSourceBuffer is disposed.
+ * The GtkSourceBufferInputStream has a strong reference to the
+ * source_buffer, so the buffer will not be finalized when the file is
+ * being saved. So there can be a temporary reference cycle.
+ */
+ GtkSourceBuffer *source_buffer;
+ GFile *file;
+
+ const GtkSourceEncoding *encoding;
+ GtkSourceNewlineType newline_type;
+ GtkSourceCompressionType compression_type;
+
+ GtkSourceMountOperationFactory mount_operation_factory;
+ gpointer mount_operation_userdata;
+
+ 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;
+
+ 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 input_stream is the
+ * GtkSourceBufferInputStream (thus in UTF-8, without compression).
+ */
+ GOutputStream *output_stream;
+ GInputStream *input_stream;
+
+ GFileInfo *info;
+
+ guint main_file : 1;
+ guint create_backup : 1;
+ 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_dup_object (value);
+ break;
+
+ case PROP_MAIN_FILE:
+ gtk_source_file_saver_set_main_file (saver, g_value_get_boolean (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_CREATE_BACKUP:
+ gtk_source_file_saver_set_create_backup (saver, g_value_get_boolean (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_MAIN_FILE:
+ g_value_set_boolean (value, saver->priv->main_file);
+ 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_CREATE_BACKUP:
+ g_value_set_boolean (value, saver->priv->create_backup);
+ 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);
+}
+
+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;
+ }
+
+ g_clear_object (&saver->priv->file);
+
+ 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->source_buffer != NULL)
+ {
+ const GtkSourceEncoding *encoding;
+ GtkSourceNewlineType newline_type;
+ GtkSourceCompressionType compression_type;
+
+ encoding = gtk_source_buffer_get_encoding (saver->priv->source_buffer);
+ gtk_source_file_saver_set_encoding (saver, encoding);
+
+ newline_type = gtk_source_buffer_get_newline_type (saver->priv->source_buffer);
+ gtk_source_file_saver_set_newline_type (saver, newline_type);
+
+ compression_type = gtk_source_buffer_get_compression_type (saver->priv->source_buffer);
+ 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.
+ *
+ * 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 #GFile where to save the buffer.
+ *
+ * Since: 3.14
+ */
+ g_object_class_install_property (object_class,
+ PROP_FILE,
+ g_param_spec_object ("file",
+ _("File"),
+ "",
+ G_TYPE_FILE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GtkSourceFileSaver:main-file:
+ *
+ * Whether the #GtkSourceFileSaver:file is the main file of the
+ * #GtkSourceFileSaver:buffer.
+ *
+ * Since: 3.14
+ */
+ g_object_class_install_property (object_class,
+ PROP_MAIN_FILE,
+ g_param_spec_boolean ("main-file",
+ _("Main file"),
+ "",
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ 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:create-backup:
+ *
+ * Create a backup before saving the file.
+ *
+ * Since: 3.14
+ */
+ g_object_class_install_property (object_class,
+ PROP_CREATE_BACKUP,
+ g_param_spec_boolean ("create-backup",
+ _("Create backup"),
+ "",
+ FALSE,
+ 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");
+ });
+
+ /* TODO update mtime stored in GtkSourceBuffer */
+ 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->file,
+ 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 (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)
+ {
+ GtkSourceBufferInputStream *buffer_stream;
+ gsize total_chars_written;
+
+ buffer_stream = GTK_SOURCE_BUFFER_INPUT_STREAM (saver->priv->input_stream);
+ total_chars_written = _gtk_source_buffer_input_stream_tell (buffer_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 (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;
+ GtkSourceBufferInputStream *buffer_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);
+ }
+
+ buffer_stream = GTK_SOURCE_BUFFER_INPUT_STREAM (saver->priv->input_stream);
+ saver->priv->total_size = _gtk_source_buffer_input_stream_get_total_size (buffer_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)
+{
+ DEBUG ({
+ g_print ("Start replacing file contents\n");
+ g_print ("Make backup: %s\n", make_backup ? "yes" : "no");
+ });
+
+ g_file_replace_async (saver->priv->file,
+ NULL,
+ saver->priv->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
+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);
+ }
+
+ begin_write (saver);
+}
+
+static GMountOperation *
+create_mount_operation (GtkSourceFileSaver *saver)
+{
+ return saver->priv->mount_operation_factory != NULL ?
+ saver->priv->mount_operation_factory (saver->priv->mount_operation_userdata) :
+ g_mount_operation_new ();
+}
+
+static void
+recover_not_mounted (GtkSourceFileSaver *saver)
+{
+ GMountOperation *mount_operation = create_mount_operation (saver);
+
+ DEBUG ({
+ g_print ("%s\n", G_STRFUNC);
+ });
+
+ saver->priv->tried_mount = TRUE;
+
+ g_file_mount_enclosing_volume (saver->priv->file,
+ G_MOUNT_MOUNT_NONE,
+ mount_operation,
+ g_task_get_cancellable (saver->priv->task),
+ (GAsyncReadyCallback) mount_cb,
+ saver);
+
+ g_object_unref (mount_operation);
+}
+
+/**
+ * gtk_source_file_saver_new:
+ * @buffer: the #GtkSourceBuffer to save.
+ * @file: the #GFile where to save the @buffer to.
+ *
+ * Returns: a new #GtkSourceFileSaver object.
+ * Since: 3.14
+ */
+GtkSourceFileSaver *
+gtk_source_file_saver_new (GtkSourceBuffer *buffer,
+ GFile *file)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL);
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+
+ return g_object_new (GTK_SOURCE_TYPE_FILE_SAVER,
+ "buffer", buffer,
+ "file", file,
+ 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 #GFile where to save the buffer to.
+ * Since: 3.14
+ */
+GFile *
+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_set_main_file:
+ * @saver: a #GtkSourceFileSaver.
+ * @main_file: whether the file is the main file of the buffer.
+ *
+ * Sets whether the #GtkSourceFileSaver:file is the main file of the
+ * #GtkSourceFileSaver:buffer. If so, the following buffer properties will be
+ * updated on a successful save: the file, the encoding, the newline type and
+ * the compression type.
+ *
+ * If @main_file is %FALSE, it means that you are saving the buffer to a
+ * secondary file, and therefore the buffer properties will not be affected.
+ *
+ * Since: 3.14
+ */
+void
+gtk_source_file_saver_set_main_file (GtkSourceFileSaver *saver,
+ gboolean main_file)
+{
+ g_return_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver));
+
+ main_file = main_file != FALSE;
+
+ if (saver->priv->main_file != main_file)
+ {
+ saver->priv->main_file = main_file;
+ g_object_notify (G_OBJECT (saver), "main-file");
+ }
+}
+
+/**
+ * gtk_source_file_saver_get_main_file:
+ * @saver: a #GtkSourceFileSaver.
+ *
+ * Returns: whether the file is the main file of the buffer.
+ * Since: 3.14
+ */
+gboolean
+gtk_source_file_saver_get_main_file (GtkSourceFileSaver *saver)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver), TRUE);
+
+ return saver->priv->main_file;
+}
+
+/**
+ * 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 #GtkSourceFileSaver:file newline type that will be used for the
+ * next file saving. It doesn't change the newline type of the
+ * #GtkSourceFileSaver:buffer.
+ *
+ * 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_create_backup:
+ * @saver: a #GtkSourceFileSaver.
+ * @create_backup: whether to create a backup before saving the file.
+ *
+ * Since: 3.14
+ */
+void
+gtk_source_file_saver_set_create_backup (GtkSourceFileSaver *saver,
+ gboolean create_backup)
+{
+ g_return_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver));
+ g_return_if_fail (saver->priv->task == NULL);
+
+ create_backup = create_backup != FALSE;
+
+ if (saver->priv->create_backup != create_backup)
+ {
+ saver->priv->create_backup = create_backup;
+ g_object_notify (G_OBJECT (saver), "create-backup");
+ }
+}
+
+/**
+ * gtk_source_file_saver_get_create_backup:
+ * @saver: a #GtkSourceFileSaver.
+ *
+ * Returns: whether to create a backup before saving the file.
+ * Since: 3.14
+ */
+gboolean
+gtk_source_file_saver_get_create_backup (GtkSourceFileSaver *saver)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver), FALSE);
+
+ return saver->priv->create_backup;
+}
+
+/**
+ * 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: (allow-none): optional #GCancellable object, %NULL to ignore.
+ * @progress_callback: (allow-none): 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.
+ * @callback: (scope async): a #GAsyncReadyCallback to call when the request is
+ * satisfied.
+ * @user_data: user data to pass to @callback.
+ *
+ * Saves asynchronously the #GtkSourceFileSaver:buffer into the
+ * #GtkSourceFileSaver:file. See the #GAsyncResult documentation to know how to
+ * use this function.
+ *
+ * Since: 3.14
+ */
+void
+gtk_source_file_saver_save_async (GtkSourceFileSaver *saver,
+ gint io_priority,
+ GCancellable *cancellable,
+ GFileProgressCallback progress_callback,
+ gpointer progress_callback_data,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ gboolean implicit_trailing_newline;
+
+ g_return_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver));
+ g_return_if_fail (saver->priv->task == NULL);
+ g_return_if_fail (saver->priv->source_buffer != NULL);
+
+ 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;
+
+ 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 source_buffer.
+ * We create the BufferInputStream here so we are sure that the
+ * source_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);
+
+ begin_write (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, and if the
+ * #GtkSourceFileSaver:main-file property is %TRUE, the following
+ * #GtkSourceBuffer properties will be updated: the file, 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->main_file && saver->priv->source_buffer != NULL)
+ {
+ _gtk_source_buffer_set_file (saver->priv->source_buffer,
+ saver->priv->file);
+
+ _gtk_source_buffer_set_encoding (saver->priv->source_buffer,
+ saver->priv->encoding);
+
+ _gtk_source_buffer_set_newline_type (saver->priv->source_buffer,
+ saver->priv->newline_type);
+
+ _gtk_source_buffer_set_compression_type (saver->priv->source_buffer,
+ saver->priv->compression_type);
+ }
+
+ reset (saver);
+
+ return ok;
+}
+
+/**
+ * gtk_source_file_saver_set_mount_operation_factory:
+ * @saver: a #GtkSourceFileSaver.
+ * @callback: a #GtkSourceMountOperationFactory to call when a #GMountOperation
+ * is needed.
+ * @user_data: the data to pass to the @callback function.
+ *
+ * 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.
+ *
+ * Since: 3.14
+ */
+void
+gtk_source_file_saver_set_mount_operation_factory (GtkSourceFileSaver *saver,
+ GtkSourceMountOperationFactory callback,
+ gpointer user_data)
+{
+ g_return_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver));
+
+ saver->priv->mount_operation_factory = callback;
+ saver->priv->mount_operation_userdata = user_data;
+}
diff --git a/gtksourceview/gtksourcefilesaver.h b/gtksourceview/gtksourcefilesaver.h
new file mode 100644
index 0000000..fbb5d27
--- /dev/null
+++ b/gtksourceview/gtksourcefilesaver.h
@@ -0,0 +1,113 @@
+/* -*- 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>
+#include <gtksourceview/gtksourcebuffer.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;
+
+struct _GtkSourceFileSaver
+{
+ GObject object;
+
+ GtkSourceFileSaverPrivate *priv;
+};
+
+struct _GtkSourceFileSaverClass
+{
+ GObjectClass parent_class;
+};
+
+GType gtk_source_file_saver_get_type (void) G_GNUC_CONST;
+
+GtkSourceFileSaver *gtk_source_file_saver_new (GtkSourceBuffer *buffer,
+ GFile *file);
+
+GtkSourceBuffer *gtk_source_file_saver_get_buffer (GtkSourceFileSaver *saver);
+
+GFile *gtk_source_file_saver_get_file (GtkSourceFileSaver *saver);
+
+void gtk_source_file_saver_set_main_file (GtkSourceFileSaver *saver,
+ gboolean main_file);
+
+gboolean gtk_source_file_saver_get_main_file (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_create_backup
+ (GtkSourceFileSaver *saver,
+ gboolean create_backup);
+
+gboolean gtk_source_file_saver_get_create_backup
+ (GtkSourceFileSaver *saver);
+
+void gtk_source_file_saver_save_async (GtkSourceFileSaver *saver,
+ gint io_priority,
+ GCancellable *cancellable,
+ GFileProgressCallback progress_callback,
+ gpointer
progress_callback_data,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean gtk_source_file_saver_save_finish (GtkSourceFileSaver *saver,
+ GAsyncResult *result,
+ GError **error);
+
+void gtk_source_file_saver_set_mount_operation_factory
+ (GtkSourceFileSaver *saver,
+ GtkSourceMountOperationFactory callback,
+ gpointer user_data);
+
+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..c644687 100644
--- a/gtksourceview/gtksourcetypes.h
+++ b/gtksourceview/gtksourcetypes.h
@@ -33,6 +33,9 @@ typedef struct _GtkSourceCompletionInfo GtkSourceCompletionInfo;
typedef struct _GtkSourceCompletionItem GtkSourceCompletionItem;
typedef struct _GtkSourceCompletionProposal GtkSourceCompletionProposal;
typedef struct _GtkSourceCompletionProvider GtkSourceCompletionProvider;
+typedef struct _GtkSourceEncoding GtkSourceEncoding;
+typedef struct _GtkSourceFileLoader GtkSourceFileLoader;
+typedef struct _GtkSourceFileSaver GtkSourceFileSaver;
typedef struct _GtkSourceGutter GtkSourceGutter;
typedef struct _GtkSourceGutterRenderer GtkSourceGutterRenderer;
typedef struct _GtkSourceGutterRendererPixbuf GtkSourceGutterRendererPixbuf;
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 6a69647..c5a8934 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,9 @@ gtksourceview/gtksourcecompletionitem.c
gtksourceview/gtksourcecompletionmodel.c
[type: gettext/glade]gtksourceview/gtksourcecompletion.ui
gtksourceview/gtksourcecontextengine.c
+gtksourceview/gtksourceencoding.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 b437759..0e783cb 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 = \
@@ -84,6 +100,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
@@ -170,6 +200,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..4f71466
--- /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;
+ GInputStream *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 (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 (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..7227581
--- /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;
+ GOutputStream *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 (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 (out, NULL, &err);
+
+ g_assert_no_error (err);
+
+ type = gtk_source_buffer_output_stream_detect_newline_type (GTK_SOURCE_BUFFER_OUTPUT_STREAM (out));
+ g_assert (type == newline_type);
+
+ g_output_stream_close (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;
+ GOutputStream *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 (out, "\r", 1, NULL, NULL);
+ g_output_stream_write (out, "\n", 1, NULL, NULL);
+
+ g_output_stream_flush (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 (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;
+ GOutputStream *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 (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 (out, NULL, &err);
+ g_assert_no_error (err);
+
+ g_output_stream_close (out, NULL, &err);
+ g_assert_no_error (err);
+
+ if (guessed != NULL)
+ {
+ *guessed = gtk_source_buffer_output_stream_get_guessed (GTK_SOURCE_BUFFER_OUTPUT_STREAM
(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..6f4ac84
--- /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 *file)
+{
+ if (g_file_query_exists (file, NULL))
+ {
+ GError *error = NULL;
+
+ g_file_delete (file, 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)
+ {
+ GtkTextBuffer *buffer;
+ GtkTextIter start;
+ GtkTextIter end;
+ gchar *buffer_contents;
+
+ g_object_get (loader, "buffer", &buffer, NULL);
+
+ gtk_text_buffer_get_bounds (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 *file;
+ GtkSourceBuffer *buffer;
+ GtkSourceFileLoader *loader;
+ GSList *candidate_encodings;
+ LoaderTestData *data;
+ GError *error = NULL;
+
+ g_file_set_contents (filename, contents, -1, &error);
+ g_assert_no_error (error);
+
+ file = g_file_new_for_path (filename);
+ buffer = gtk_source_buffer_new (NULL);
+
+ loader = gtk_source_file_loader_new (buffer, file);
+
+ 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,
+ (GAsyncReadyCallback) load_file_cb,
+ data);
+
+ gtk_main ();
+
+ g_slice_free (LoaderTestData, data);
+ delete_file (file);
+ g_object_unref (file);
+ g_object_unref (buffer);
+ 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..cf4b0c5
--- /dev/null
+++ b/tests/test-file-saver.c
@@ -0,0 +1,759 @@
+/* -*- 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 *file;
+ 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->file));
+
+ if (data->saved_callback != NULL)
+ {
+ data->saved_callback (data);
+ }
+
+ if (!data->file_existed)
+ {
+ g_file_delete (data->file, NULL, NULL);
+ }
+
+ /* finished */
+ gtk_main_quit ();
+}
+
+static void
+save_file (SaverTestData *data)
+{
+ data->file_existed = g_file_query_exists (data->file, NULL);
+
+ gtk_source_file_saver_save_async (data->saver,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ NULL,
+ NULL,
+ (GAsyncReadyCallback) save_file_cb,
+ data);
+}
+
+static void
+mount_cb (GFile *file,
+ GAsyncResult *result,
+ SaverTestData *data)
+{
+ GError *error = NULL;
+
+ g_file_mount_enclosing_volume_finish (file, 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->file))
+ {
+ save_file (data);
+ return;
+ }
+
+ mount_operation = gtk_mount_operation_new (NULL);
+
+ g_file_mount_enclosing_volume (data->file,
+ 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 *file;
+ GtkSourceBuffer *buffer;
+ GtkSourceFileSaver *saver;
+ SaverTestData *data;
+
+ file = 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);
+
+ saver = gtk_source_file_saver_new (buffer, file);
+
+ g_object_set (saver,
+ "newline-type", newline_type,
+ "encoding", gtk_source_encoding_get_utf8 (),
+ NULL);
+
+ data = g_slice_new (SaverTestData);
+ data->saver = saver;
+ data->file = file;
+ data->expected_file_contents = expected_file_contents;
+ data->saved_callback = saved_callback;
+ data->userdata = userdata;
+
+ check_mounted (data);
+ gtk_main ();
+
+ g_object_unref (file);
+ g_object_unref (buffer);
+ 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->file, permissions);
+}
+
+static void
+test_permissions (const gchar *uri,
+ guint permissions)
+{
+ GError *error = NULL;
+ GFile *file = g_file_new_for_commandline_arg (uri);
+ GFileOutputStream *stream;
+ GFileInfo *info;
+ guint mode;
+
+ g_file_delete (file, NULL, NULL);
+ stream = g_file_create (file, 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 (file,
+ 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 (file,
+ G_FILE_ATTRIBUTE_UNIX_MODE,
+ (mode & ~ACCESSPERMS) | permissions,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &error);
+ g_assert_no_error (error);
+
+ check_permissions (file, 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 (file, NULL, NULL);
+ g_object_unref (file);
+}
+
+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->file,
+ 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]