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