[gitg] Improved charset conversion (fixes #623466)



commit 2d95ef73eddbae1e5d7f6eae20552cc91cf9bcd6
Author: Jesse van den Kieboom <jessevdk gnome org>
Date:   Sat Oct 23 18:41:26 2010 +0200

    Improved charset conversion (fixes #623466)
    
    This adds better support for charset conversion and preservation of
    line endings. Most code was copied from gedit (GeditEncoding and
    GeditSmartCharsetConverter)

 configure.ac                           |   14 +-
 gitg/gitg-commit-view.c                |   45 ++--
 libgitg/Makefile.am                    |   48 ++--
 libgitg/gitg-encodings.c               |  547 ++++++++++++++++++++++++++++++++
 libgitg/gitg-encodings.h               |   72 +++++
 libgitg/gitg-runner.c                  |  330 ++++++++++++++-----
 libgitg/gitg-runner.h                  |    5 +
 libgitg/gitg-smart-charset-converter.c |  427 +++++++++++++++++++++++++
 libgitg/gitg-smart-charset-converter.h |   76 +++++
 9 files changed, 1427 insertions(+), 137 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 4a1638f..5aa6cd5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -64,14 +64,16 @@ PKG_CHECK_EXISTS([gtk+-3.0 >= 2.90],
 			GTKSOURCEVIEW_REQUIRED_VERSION=2.8.0
 		 ])
 
+GLIB_REQUIRED_VERSION=2.24
+
 PKG_CHECK_MODULES(GITG, [
 	$GTK_REQUIRED >= $GTK_REQUIRED_VERSION
-	gthread-2.0
-	glib-2.0
-	gobject-2.0
-	gmodule-2.0
-	gio-2.0
-	gio-unix-2.0
+	gthread-2.0 >= $GLIB_REQUIRED_VERSION
+	glib-2.0 >= $GLIB_REQUIRED_VERSION
+	gobject-2.0 >= $GLIB_REQUIRED_VERSION
+	gmodule-2.0 >= $GLIB_REQUIRED_VERSION
+	gio-2.0 >= $GLIB_REQUIRED_VERSION
+	gio-unix-2.0 >= $GLIB_REQUIRED_VERSION
 ])
 
 PKG_CHECK_MODULES(PACKAGE, [
diff --git a/gitg/gitg-commit-view.c b/gitg/gitg-commit-view.c
index 276d79e..38bc044 100644
--- a/gitg/gitg-commit-view.c
+++ b/gitg/gitg-commit-view.c
@@ -253,7 +253,6 @@ on_changes_update (GitgRunner *runner, gchar **buffer, GitgCommitView *view)
 		}
 
 		gtk_text_buffer_insert (buf, &iter, line, -1);
-		gtk_text_buffer_insert (buf, &iter, "\n", -1);
 	}
 
 	if (gtk_source_buffer_get_language (GTK_SOURCE_BUFFER(buf)) == NULL)
@@ -380,8 +379,8 @@ get_selected_files(GtkTreeView             *tree_view,
 }
 
 static gboolean
-check_selection(GtkTreeView    *tree_view, 
-                GtkTreeIter    *iter, 
+check_selection(GtkTreeView    *tree_view,
+                GtkTreeIter    *iter,
                 GitgCommitView *view)
 {
 	if (view->priv->update_id)
@@ -769,10 +768,10 @@ line_patch_contents (GitgCommitView *view,
 			    line_type == old_type || is_patch_line)
 			{
 				/* copy context */
+				gtk_text_iter_forward_line (&line_end);
 				text = gtk_text_buffer_get_text (buffer, &start, &line_end, TRUE);
 
 				g_string_append (patch, text);
-				g_string_append_c (patch, '\n');
 
 				if (!is_patch_line || line_type == GITG_DIFF_LINE_TYPE_REMOVE)
 				{
@@ -1298,7 +1297,7 @@ on_tree_view_unstaged_drag_data_received(GtkWidget        *widget,
 }
 
 static void
-initialize_dnd(GitgCommitView *view, 
+initialize_dnd(GitgCommitView *view,
                GtkTreeView    *tree_view,
                GCallback       drag_data_received)
 {
@@ -1661,10 +1660,12 @@ gitg_commit_view_init (GitgCommitView *self)
 	self->priv = GITG_COMMIT_VIEW_GET_PRIVATE (self);
 
 	self->priv->runner = gitg_runner_new (10000);
+	gitg_runner_set_preserve_line_endings (self->priv->runner, TRUE);
+
 	self->priv->hand = gdk_cursor_new (GDK_HAND1);
 }
 
-void 
+void
 gitg_commit_view_set_repository(GitgCommitView *view, GitgRepository *repository)
 {
 	g_return_if_fail(GITG_IS_COMMIT_VIEW(view));
@@ -1983,7 +1984,7 @@ show_error(GitgCommitView *view, gchar const *error)
 	gtk_widget_destroy(dlg);
 }
 
-static void 
+static void
 on_commit_clicked(GtkButton *button, GitgCommitView *view)
 {
 	if (!gitg_commit_has_changes(view->priv->commit))
@@ -2025,7 +2026,7 @@ on_commit_clicked(GtkButton *button, GitgCommitView *view)
 	g_free(comment);
 }
 
-static void 
+static void
 on_context_value_changed(GtkHScale *scale, GitgCommitView *view)
 {
 	view->priv->context_size = (gint)gtk_range_get_value(GTK_RANGE(scale));
@@ -2088,16 +2089,16 @@ popup_unstaged_menu(GitgCommitView *view, GdkEventButton *event)
 	}
 	else
 	{
-		gtk_menu_popup(GTK_MENU(wd), NULL, NULL, 
-					   gitg_utils_menu_position_under_tree_view, 
-					   view->priv->tree_view_staged, 0, 
+		gtk_menu_popup(GTK_MENU(wd), NULL, NULL,
+					   gitg_utils_menu_position_under_tree_view,
+					   view->priv->tree_view_staged, 0,
 					   gtk_get_current_event_time());
 	}
 
 	return TRUE;
 }
 
-static gboolean 
+static gboolean
 popup_staged_menu(GitgCommitView *view, GdkEventButton *event)
 {
 	if (!set_staged_popup_status(view))
@@ -2113,9 +2114,9 @@ popup_staged_menu(GitgCommitView *view, GdkEventButton *event)
 	}
 	else
 	{
-		gtk_menu_popup(GTK_MENU(wd), NULL, NULL, 
-					   gitg_utils_menu_position_under_tree_view, 
-					   view->priv->tree_view_unstaged, 0, 
+		gtk_menu_popup(GTK_MENU(wd), NULL, NULL,
+					   gitg_utils_menu_position_under_tree_view,
+					   view->priv->tree_view_unstaged, 0,
 					   gtk_get_current_event_time());
 	}
 
@@ -2123,19 +2124,19 @@ popup_staged_menu(GitgCommitView *view, GdkEventButton *event)
 }
 
 
-static gboolean 
+static gboolean
 on_unstaged_popup_menu(GtkWidget *widget, GitgCommitView *view)
 {
 	return popup_unstaged_menu(view, NULL);
 }
 
-static gboolean 
+static gboolean
 on_staged_popup_menu(GtkWidget *widget, GitgCommitView *view)
 {
 	return popup_staged_menu(view, NULL);
 }
 
-static void 
+static void
 on_stage_changes(GtkAction *action, GitgCommitView *view)
 {
 	if (view->priv->context_type == CONTEXT_TYPE_FILE)
@@ -2197,7 +2198,7 @@ do_revert_changes(GitgCommitView *view)
 		show_error(view, _("Revert fail"));
 }
 
-static void 
+static void
 on_revert_changes(GtkAction *action, GitgCommitView *view)
 {
 	GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(view))),
@@ -2207,7 +2208,7 @@ on_revert_changes(GtkAction *action, GitgCommitView *view)
 											   "%s",
 											   _("Are you sure you want to revert these changes?"));
 
-	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s", 
+	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s",
 											 _("Reverting changes is permanent and cannot be undone"));
 
 	gint response = gtk_dialog_run(GTK_DIALOG(dialog));
@@ -2252,7 +2253,7 @@ on_edit_file (GtkAction *action, GitgCommitView *view)
 	g_list_free (files);
 }
 
-static void 
+static void
 on_ignore_file (GtkAction *action, GitgCommitView *view)
 {
 	GList *files = NULL;
@@ -2300,7 +2301,7 @@ create_context_menu_item (GitgCommitView *view, gchar const *action)
 	return gtk_action_create_menu_item (ac);
 }
 
-static void 
+static void
 on_changes_view_popup_menu (GtkTextView *textview, GtkMenu *menu, GitgCommitView *view)
 {
 	gboolean is_hunk;
diff --git a/libgitg/Makefile.am b/libgitg/Makefile.am
index 0ec1f44..cc0c2e8 100644
--- a/libgitg/Makefile.am
+++ b/libgitg/Makefile.am
@@ -32,28 +32,32 @@ INST_H_FILES =			\
 	gitg-revision.h		\
 	gitg-runner.h
 
-NOINST_H_FILES =		\
-	gitg-convert.h		\
-	gitg-debug.h		\
-	gitg-i18n.h		\
-	gitg-lanes.h
-
-C_FILES =			\
-	$(BUILT_SOURCES)	\
-	gitg-changed-file.c	\
-	gitg-color.c		\
-	gitg-commit.c		\
-	gitg-config.c		\
-	gitg-convert.c		\
-	gitg-debug.c		\
-	gitg-hash.c		\
-	gitg-i18n.c		\
-	gitg-lane.c		\
-	gitg-lanes.c		\
-	gitg-ref.c		\
-	gitg-repository.c	\
-	gitg-revision.c		\
-	gitg-runner.c
+NOINST_H_FILES =			\
+	gitg-convert.h			\
+	gitg-debug.h			\
+	gitg-i18n.h			\
+	gitg-lanes.h			\
+	gitg-smart-charset-converter.h	\
+	gitg-encodings.h
+
+C_FILES =				\
+	$(BUILT_SOURCES)		\
+	gitg-changed-file.c		\
+	gitg-color.c			\
+	gitg-commit.c			\
+	gitg-config.c			\
+	gitg-convert.c			\
+	gitg-debug.c			\
+	gitg-hash.c			\
+	gitg-i18n.c			\
+	gitg-lane.c			\
+	gitg-lanes.c			\
+	gitg-ref.c			\
+	gitg-repository.c		\
+	gitg-revision.c			\
+	gitg-runner.c			\
+	gitg-smart-charset-converter.c	\
+	gitg-encodings.c
 
 ENUM_H_FILES =			\
 	gitg-changed-file.h
diff --git a/libgitg/gitg-encodings.c b/libgitg/gitg-encodings.c
new file mode 100644
index 0000000..48c8988
--- /dev/null
+++ b/libgitg/gitg-encodings.c
@@ -0,0 +1,547 @@
+/*
+ * This file was copied from gedit-encodings.c
+ *
+ * gedit-encodings.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2002-2005 Paolo Maggi 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, 
+ * Boston, MA 02111-1307, USA.
+ */
+ 
+/*
+ * Modified by the gedit Team, 2002-2005. See the AUTHORS file for a 
+ * list of people on the gedit Team.  
+ * See the ChangeLog files for a list of changes. 
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+
+#include "gitg-encodings.h"
+
+
+struct _GitgEncoding
+{
+	gint   index;
+	const gchar *charset;
+	const gchar *name;
+};
+
+G_DEFINE_BOXED_TYPE (GitgEncoding, gitg_encoding,
+                     gitg_encoding_copy,
+                     gitg_encoding_free)
+
+/* 
+ * The original versions of the following tables are taken from profterm 
+ *
+ * Copyright (C) 2002 Red Hat, Inc.
+ */
+
+typedef enum
+{
+
+  GITG_ENCODING_ISO_8859_1,
+  GITG_ENCODING_ISO_8859_2,
+  GITG_ENCODING_ISO_8859_3,
+  GITG_ENCODING_ISO_8859_4,
+  GITG_ENCODING_ISO_8859_5,
+  GITG_ENCODING_ISO_8859_6,
+  GITG_ENCODING_ISO_8859_7,
+  GITG_ENCODING_ISO_8859_8,
+  GITG_ENCODING_ISO_8859_9,
+  GITG_ENCODING_ISO_8859_10,
+  GITG_ENCODING_ISO_8859_13,
+  GITG_ENCODING_ISO_8859_14,
+  GITG_ENCODING_ISO_8859_15,
+  GITG_ENCODING_ISO_8859_16,
+
+  GITG_ENCODING_UTF_7,
+  GITG_ENCODING_UTF_16,
+  GITG_ENCODING_UTF_16_BE,
+  GITG_ENCODING_UTF_16_LE,
+  GITG_ENCODING_UTF_32,
+  GITG_ENCODING_UCS_2,
+  GITG_ENCODING_UCS_4,
+
+  GITG_ENCODING_ARMSCII_8,
+  GITG_ENCODING_BIG5,
+  GITG_ENCODING_BIG5_HKSCS,
+  GITG_ENCODING_CP_866,
+
+  GITG_ENCODING_EUC_JP,
+  GITG_ENCODING_EUC_JP_MS,
+  GITG_ENCODING_CP932,
+  GITG_ENCODING_EUC_KR,
+  GITG_ENCODING_EUC_TW,
+
+  GITG_ENCODING_GB18030,
+  GITG_ENCODING_GB2312,
+  GITG_ENCODING_GBK,
+  GITG_ENCODING_GEOSTD8,
+
+  GITG_ENCODING_IBM_850,
+  GITG_ENCODING_IBM_852,
+  GITG_ENCODING_IBM_855,
+  GITG_ENCODING_IBM_857,
+  GITG_ENCODING_IBM_862,
+  GITG_ENCODING_IBM_864,
+
+  GITG_ENCODING_ISO_2022_JP,
+  GITG_ENCODING_ISO_2022_KR,
+  GITG_ENCODING_ISO_IR_111,
+  GITG_ENCODING_JOHAB,
+  GITG_ENCODING_KOI8_R,
+  GITG_ENCODING_KOI8__R,
+  GITG_ENCODING_KOI8_U,
+  
+  GITG_ENCODING_SHIFT_JIS,
+  GITG_ENCODING_TCVN,
+  GITG_ENCODING_TIS_620,
+  GITG_ENCODING_UHC,
+  GITG_ENCODING_VISCII,
+
+  GITG_ENCODING_WINDOWS_1250,
+  GITG_ENCODING_WINDOWS_1251,
+  GITG_ENCODING_WINDOWS_1252,
+  GITG_ENCODING_WINDOWS_1253,
+  GITG_ENCODING_WINDOWS_1254,
+  GITG_ENCODING_WINDOWS_1255,
+  GITG_ENCODING_WINDOWS_1256,
+  GITG_ENCODING_WINDOWS_1257,
+  GITG_ENCODING_WINDOWS_1258,
+
+  GITG_ENCODING_LAST,
+
+  GITG_ENCODING_UTF_8,
+  GITG_ENCODING_UNKNOWN
+  
+} GitgEncodingIndex;
+
+static const GitgEncoding utf8_encoding =  {
+	GITG_ENCODING_UTF_8,
+	"UTF-8",
+	N_("Unicode")
+};
+
+/* initialized in gitg_encoding_lazy_init() */
+static GitgEncoding unknown_encoding = {
+	GITG_ENCODING_UNKNOWN,
+	NULL, 
+	NULL 
+};
+
+static const GitgEncoding encodings [] = {
+
+  { GITG_ENCODING_ISO_8859_1,
+    "ISO-8859-1", N_("Western") },
+  { GITG_ENCODING_ISO_8859_2,
+   "ISO-8859-2", N_("Central European") },
+  { GITG_ENCODING_ISO_8859_3,
+    "ISO-8859-3", N_("South European") },
+  { GITG_ENCODING_ISO_8859_4,
+    "ISO-8859-4", N_("Baltic") },
+  { GITG_ENCODING_ISO_8859_5,
+    "ISO-8859-5", N_("Cyrillic") },
+  { GITG_ENCODING_ISO_8859_6,
+    "ISO-8859-6", N_("Arabic") },
+  { GITG_ENCODING_ISO_8859_7,
+    "ISO-8859-7", N_("Greek") },
+  { GITG_ENCODING_ISO_8859_8,
+    "ISO-8859-8", N_("Hebrew Visual") },
+  { GITG_ENCODING_ISO_8859_9,
+    "ISO-8859-9", N_("Turkish") },
+  { GITG_ENCODING_ISO_8859_10,
+    "ISO-8859-10", N_("Nordic") },
+  { GITG_ENCODING_ISO_8859_13,
+    "ISO-8859-13", N_("Baltic") },
+  { GITG_ENCODING_ISO_8859_14,
+    "ISO-8859-14", N_("Celtic") },
+  { GITG_ENCODING_ISO_8859_15,
+    "ISO-8859-15", N_("Western") },
+  { GITG_ENCODING_ISO_8859_16,
+    "ISO-8859-16", N_("Romanian") },
+
+  { GITG_ENCODING_UTF_7,
+    "UTF-7", N_("Unicode") },
+  { GITG_ENCODING_UTF_16,
+    "UTF-16", N_("Unicode") },
+  { GITG_ENCODING_UTF_16_BE,
+    "UTF-16BE", N_("Unicode") },
+  { GITG_ENCODING_UTF_16_LE,
+    "UTF-16LE", N_("Unicode") },
+  { GITG_ENCODING_UTF_32,
+    "UTF-32", N_("Unicode") },
+  { GITG_ENCODING_UCS_2,
+    "UCS-2", N_("Unicode") },
+  { GITG_ENCODING_UCS_4,
+    "UCS-4", N_("Unicode") },
+
+  { GITG_ENCODING_ARMSCII_8,
+    "ARMSCII-8", N_("Armenian") },
+  { GITG_ENCODING_BIG5,
+    "BIG5", N_("Chinese Traditional") },
+  { GITG_ENCODING_BIG5_HKSCS,
+    "BIG5-HKSCS", N_("Chinese Traditional") },
+  { GITG_ENCODING_CP_866,
+    "CP866", N_("Cyrillic/Russian") },
+
+  { GITG_ENCODING_EUC_JP,
+    "EUC-JP", N_("Japanese") },
+  { GITG_ENCODING_EUC_JP_MS,
+    "EUC-JP-MS", N_("Japanese") },
+  { GITG_ENCODING_CP932,
+    "CP932", N_("Japanese") },
+
+  { GITG_ENCODING_EUC_KR,
+    "EUC-KR", N_("Korean") },
+  { GITG_ENCODING_EUC_TW,
+    "EUC-TW", N_("Chinese Traditional") },
+
+  { GITG_ENCODING_GB18030,
+    "GB18030", N_("Chinese Simplified") },
+  { GITG_ENCODING_GB2312,
+    "GB2312", N_("Chinese Simplified") },
+  { GITG_ENCODING_GBK,
+    "GBK", N_("Chinese Simplified") },
+  { GITG_ENCODING_GEOSTD8,
+    "GEORGIAN-ACADEMY", N_("Georgian") }, /* FIXME GEOSTD8 ? */
+
+  { GITG_ENCODING_IBM_850,
+    "IBM850", N_("Western") },
+  { GITG_ENCODING_IBM_852,
+    "IBM852", N_("Central European") },
+  { GITG_ENCODING_IBM_855,
+    "IBM855", N_("Cyrillic") },
+  { GITG_ENCODING_IBM_857,
+    "IBM857", N_("Turkish") },
+  { GITG_ENCODING_IBM_862,
+    "IBM862", N_("Hebrew") },
+  { GITG_ENCODING_IBM_864,
+    "IBM864", N_("Arabic") },
+
+  { GITG_ENCODING_ISO_2022_JP,
+    "ISO-2022-JP", N_("Japanese") },
+  { GITG_ENCODING_ISO_2022_KR,
+    "ISO-2022-KR", N_("Korean") },
+  { GITG_ENCODING_ISO_IR_111,
+    "ISO-IR-111", N_("Cyrillic") },
+  { GITG_ENCODING_JOHAB,
+    "JOHAB", N_("Korean") },
+  { GITG_ENCODING_KOI8_R,
+    "KOI8R", N_("Cyrillic") },
+  { GITG_ENCODING_KOI8__R,
+    "KOI8-R", N_("Cyrillic") },
+  { GITG_ENCODING_KOI8_U,
+    "KOI8U", N_("Cyrillic/Ukrainian") },
+  
+  { GITG_ENCODING_SHIFT_JIS,
+    "SHIFT_JIS", N_("Japanese") },
+  { GITG_ENCODING_TCVN,
+    "TCVN", N_("Vietnamese") },
+  { GITG_ENCODING_TIS_620,
+    "TIS-620", N_("Thai") },
+  { GITG_ENCODING_UHC,
+    "UHC", N_("Korean") },
+  { GITG_ENCODING_VISCII,
+    "VISCII", N_("Vietnamese") },
+
+  { GITG_ENCODING_WINDOWS_1250,
+    "WINDOWS-1250", N_("Central European") },
+  { GITG_ENCODING_WINDOWS_1251,
+    "WINDOWS-1251", N_("Cyrillic") },
+  { GITG_ENCODING_WINDOWS_1252,
+    "WINDOWS-1252", N_("Western") },
+  { GITG_ENCODING_WINDOWS_1253,
+    "WINDOWS-1253", N_("Greek") },
+  { GITG_ENCODING_WINDOWS_1254,
+    "WINDOWS-1254", N_("Turkish") },
+  { GITG_ENCODING_WINDOWS_1255,
+    "WINDOWS-1255", N_("Hebrew") },
+  { GITG_ENCODING_WINDOWS_1256,
+    "WINDOWS-1256", N_("Arabic") },
+  { GITG_ENCODING_WINDOWS_1257,
+    "WINDOWS-1257", N_("Baltic") },
+  { GITG_ENCODING_WINDOWS_1258,
+    "WINDOWS-1258", N_("Vietnamese") }
+};
+
+static void
+gitg_encoding_lazy_init (void)
+{
+	static gboolean initialized = FALSE;
+	const gchar *locale_charset;
+
+	if (initialized)
+		return;
+
+	if (g_get_charset (&locale_charset) == FALSE)
+	{
+		unknown_encoding.charset = g_strdup (locale_charset);
+	}
+
+	initialized = TRUE;
+}
+
+const GitgEncoding *
+gitg_encoding_get_from_charset (const gchar *charset)
+{
+	gint i;
+
+	g_return_val_if_fail (charset != NULL, NULL);
+
+	gitg_encoding_lazy_init ();
+
+	if (charset == NULL)
+		return NULL;
+
+	if (g_ascii_strcasecmp (charset, "UTF-8") == 0)
+		return gitg_encoding_get_utf8 ();
+
+	i = 0; 
+	while (i < GITG_ENCODING_LAST)
+	{
+		if (g_ascii_strcasecmp (charset, encodings[i].charset) == 0)
+			return &encodings[i];
+      
+		++i;
+	}
+
+	if (unknown_encoding.charset != NULL)
+	{
+		if (g_ascii_strcasecmp (charset, unknown_encoding.charset) == 0)
+			return &unknown_encoding;
+	}
+
+	return NULL;
+}
+
+GSList *
+gitg_encoding_get_candidates (void)
+{
+	static GSList *ret = NULL;
+
+	if (ret == NULL)
+	{
+		ret = g_slist_prepend (ret,
+		                       (gpointer)gitg_encoding_get_from_index (GITG_ENCODING_WINDOWS_1250));
+
+		ret = g_slist_prepend (ret,
+		                       (gpointer)gitg_encoding_get_from_index (GITG_ENCODING_ISO_8859_1));
+
+		ret = g_slist_prepend (ret,
+		                       (gpointer)gitg_encoding_get_current ());
+	}
+
+	return ret;
+}
+
+const GitgEncoding *
+gitg_encoding_get_from_index (gint idx)
+{
+	g_return_val_if_fail (idx >= 0, NULL);
+
+	if (idx >= GITG_ENCODING_LAST)
+		return NULL;
+
+	gitg_encoding_lazy_init ();
+
+	return &encodings[idx];
+}
+
+const GitgEncoding *
+gitg_encoding_get_utf8 (void)
+{
+	gitg_encoding_lazy_init ();
+
+	return &utf8_encoding;
+}
+
+const GitgEncoding *
+gitg_encoding_get_current (void)
+{
+	static gboolean initialized = FALSE;
+	static const GitgEncoding *locale_encoding = NULL;
+
+	const gchar *locale_charset;
+
+	gitg_encoding_lazy_init ();
+
+	if (initialized != FALSE)
+		return locale_encoding;
+
+	if (g_get_charset (&locale_charset) == FALSE) 
+	{
+		g_return_val_if_fail (locale_charset != NULL, &utf8_encoding);
+		
+		locale_encoding = gitg_encoding_get_from_charset (locale_charset);
+	}
+	else
+	{
+		locale_encoding = &utf8_encoding;
+	}
+	
+	if (locale_encoding == NULL)
+	{
+		locale_encoding = &unknown_encoding;
+	}
+
+	g_return_val_if_fail (locale_encoding != NULL, NULL);
+
+	initialized = TRUE;
+
+	return locale_encoding;
+}
+
+gchar *
+gitg_encoding_to_string (const GitgEncoding* enc)
+{
+	g_return_val_if_fail (enc != NULL, NULL);
+	
+	gitg_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 *
+gitg_encoding_get_charset (const GitgEncoding* enc)
+{
+	g_return_val_if_fail (enc != NULL, NULL);
+
+	gitg_encoding_lazy_init ();
+
+	g_return_val_if_fail (enc->charset != NULL, NULL);
+
+	return enc->charset;
+}
+
+const gchar *
+gitg_encoding_get_name (const GitgEncoding* enc)
+{
+	g_return_val_if_fail (enc != NULL, NULL);
+
+	gitg_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 fres() doesn't
+ * do nothing */
+
+GitgEncoding *
+gitg_encoding_copy (const GitgEncoding *enc)
+{
+	g_return_val_if_fail (enc != NULL, NULL);
+
+	return (GitgEncoding *) enc;
+}
+
+void 
+gitg_encoding_free (GitgEncoding *enc)
+{
+	g_return_if_fail (enc != NULL);
+}
+
+static gboolean
+data_exists (GSList         *list,
+	     const gpointer  data)
+{
+	while (list != NULL)
+	{
+		if (list->data == data)
+			return TRUE;
+
+		list = g_slist_next (list);
+	}
+
+	return FALSE;
+}
+
+GSList *
+_gitg_encoding_strv_to_list (const gchar * const *enc_str)
+{
+	GSList *res = NULL;
+	gchar **p;
+	const GitgEncoding *enc;
+	
+	for (p = (gchar **)enc_str; p != NULL && *p != NULL; p++)
+	{
+		const gchar *charset = *p;
+
+		if (strcmp (charset, "CURRENT") == 0)
+			g_get_charset (&charset);
+
+		g_return_val_if_fail (charset != NULL, NULL);
+		enc = gitg_encoding_get_from_charset (charset);
+
+		if (enc != NULL)
+		{
+			if (!data_exists (res, (gpointer)enc))
+				res = g_slist_prepend (res, (gpointer)enc);
+
+		}
+	}
+
+	return g_slist_reverse (res);
+}
+
+gchar **
+_gitg_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 GitgEncoding *enc;
+		const gchar *charset;
+		
+		enc = (const GitgEncoding *)l->data;
+
+		charset = gitg_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);
+}
+
+/* ex:ts=8:noet: */
diff --git a/libgitg/gitg-encodings.h b/libgitg/gitg-encodings.h
new file mode 100644
index 0000000..1c7070e
--- /dev/null
+++ b/libgitg/gitg-encodings.h
@@ -0,0 +1,72 @@
+/*
+ * Copied from gedit-encodings.h
+ *
+ *
+ * gedit-encodings.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the gedit Team, 2002-2005. See the AUTHORS file for a
+ * list of people on the gedit Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __GITG_ENCODINGS_H__
+#define __GITG_ENCODINGS_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GitgEncoding GitgEncoding;
+
+#define GITG_TYPE_ENCODING     (gitg_encoding_get_type ())
+
+GType              	 gitg_encoding_get_type	 (void) G_GNUC_CONST;
+
+const GitgEncoding	*gitg_encoding_get_from_charset (const gchar         *charset);
+const GitgEncoding	*gitg_encoding_get_from_index	 (gint                 index);
+
+gchar 			*gitg_encoding_to_string	 (const GitgEncoding *enc);
+
+const gchar		*gitg_encoding_get_name	 (const GitgEncoding *enc);
+const gchar		*gitg_encoding_get_charset	 (const GitgEncoding *enc);
+
+const GitgEncoding 	*gitg_encoding_get_utf8	 (void);
+const GitgEncoding 	*gitg_encoding_get_current	 (void);
+
+GSList                  *gitg_encoding_get_candidates (void);
+
+/* These should not be used, they are just to make python bindings happy */
+GitgEncoding		*gitg_encoding_copy		 (const GitgEncoding *enc);
+void               	 gitg_encoding_free		 (GitgEncoding       *enc);
+
+GSList			*_gitg_encoding_strv_to_list    (const gchar * const *enc_str);
+gchar		       **_gitg_encoding_list_to_strv	 (const GSList        *enc);
+
+G_END_DECLS
+
+#endif  /* __GITG_ENCODINGS_H__ */
+
+/* ex:ts=8:noet: */
diff --git a/libgitg/gitg-runner.c b/libgitg/gitg-runner.c
index 22e0198..0b4371e 100644
--- a/libgitg/gitg-runner.c
+++ b/libgitg/gitg-runner.c
@@ -23,6 +23,7 @@
 #include "gitg-convert.h"
 #include "gitg-debug.h"
 #include "gitg-runner.h"
+#include "gitg-smart-charset-converter.h"
 
 #include <string.h>
 #include <sys/types.h>
@@ -53,7 +54,8 @@ enum
 	PROP_0,
 
 	PROP_BUFFER_SIZE,
-	PROP_SYNCHRONIZED
+	PROP_SYNCHRONIZED,
+	PROP_PRESERVE_LINE_ENDINGS
 };
 
 struct _GitgRunnerPrivate
@@ -62,15 +64,19 @@ struct _GitgRunnerPrivate
 	GInputStream *input_stream;
 	GOutputStream *output_stream;
 	GCancellable *cancellable;
-	gboolean synchronized;
 
 	guint buffer_size;
-	gchar *buffer;
 	gchar *read_buffer;
 	gchar **lines;
 	gchar **environment;
 
+	gchar *rest_buffer;
+	gssize rest_buffer_size;
+
 	gint exit_status;
+
+	guint synchronized : 1;
+	guint preserve_line_endings : 1;
 };
 
 G_DEFINE_TYPE (GitgRunner, gitg_runner, G_TYPE_OBJECT)
@@ -82,7 +88,8 @@ typedef struct
 } AsyncData;
 
 static AsyncData *
-async_data_new (GitgRunner *runner, GCancellable *cancellable)
+async_data_new (GitgRunner   *runner,
+                GCancellable *cancellable)
 {
 	AsyncData *data = g_slice_new (AsyncData);
 	data->runner = runner;
@@ -112,7 +119,9 @@ gitg_runner_error_quark (void)
 }
 
 static void
-runner_io_exit (GPid pid, gint status, GitgRunner *runner)
+runner_io_exit (GPid        pid,
+                gint        status,
+                GitgRunner *runner)
 {
 	g_spawn_close_pid (pid);
 
@@ -152,7 +161,7 @@ gitg_runner_finalize (GObject *object)
 	g_slice_free1 (sizeof (gchar *) * (runner->priv->buffer_size + 1), runner->priv->lines);
 
 	/* Remove line buffer */
-	g_free (runner->priv->buffer);
+	g_free (runner->priv->rest_buffer);
 	g_strfreev (runner->priv->environment);
 
 	g_object_unref (runner->priv->cancellable);
@@ -161,7 +170,10 @@ gitg_runner_finalize (GObject *object)
 }
 
 static void
-gitg_runner_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+gitg_runner_get_property (GObject    *object,
+                          guint       prop_id,
+                          GValue     *value,
+                          GParamSpec *pspec)
 {
 	GitgRunner *runner = GITG_RUNNER (object);
 
@@ -173,6 +185,9 @@ gitg_runner_get_property (GObject *object, guint prop_id, GValue *value, GParamS
 		case PROP_SYNCHRONIZED:
 			g_value_set_boolean (value, runner->priv->synchronized);
 			break;
+		case PROP_PRESERVE_LINE_ENDINGS:
+			g_value_set_boolean (value, runner->priv->preserve_line_endings);
+			break;
 		default:
 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 			break;
@@ -180,7 +195,8 @@ gitg_runner_get_property (GObject *object, guint prop_id, GValue *value, GParamS
 }
 
 static void
-set_buffer_size (GitgRunner *runner, guint buffer_size)
+set_buffer_size (GitgRunner *runner,
+                 guint       buffer_size)
 {
 	runner->priv->buffer_size = buffer_size;
 	runner->priv->lines = g_slice_alloc (sizeof (gchar *) * (runner->priv->buffer_size + 1));
@@ -190,7 +206,10 @@ set_buffer_size (GitgRunner *runner, guint buffer_size)
 }
 
 static void
-gitg_runner_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+gitg_runner_set_property (GObject      *object,
+                          guint         prop_id,
+                          const GValue *value,
+                          GParamSpec   *pspec)
 {
 	GitgRunner *runner = GITG_RUNNER (object);
 
@@ -202,6 +221,9 @@ gitg_runner_set_property (GObject *object, guint prop_id, const GValue *value, G
 		case PROP_SYNCHRONIZED:
 			runner->priv->synchronized = g_value_get_boolean (value);
 			break;
+		case PROP_PRESERVE_LINE_ENDINGS:
+			runner->priv->preserve_line_endings = g_value_get_boolean (value);
+			break;
 		default:
 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 			break;
@@ -273,6 +295,14 @@ gitg_runner_class_init (GitgRunnerClass *klass)
 		              G_TYPE_BOOLEAN);
 
 	g_type_class_add_private (object_class, sizeof (GitgRunnerPrivate));
+
+	g_object_class_install_property (object_class,
+	                                 PROP_PRESERVE_LINE_ENDINGS,
+	                                 g_param_spec_boolean ("preserve-line-endings",
+	                                                       "Preserve Line Endings",
+	                                                       "preserve line endings",
+	                                                       FALSE,
+	                                                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
 }
 
 static void
@@ -309,72 +339,143 @@ gitg_runner_new_synchronized (guint buffer_size)
 	                                  NULL));
 }
 
+void
+gitg_runner_set_preserve_line_endings (GitgRunner *runner,
+                                       gboolean    preserve_line_endings)
+{
+	g_return_if_fail (GITG_IS_RUNNER (runner));
+
+	runner->priv->preserve_line_endings = preserve_line_endings;
+	g_object_notify (G_OBJECT (runner), "preserve-line-endings");
+}
+
+gboolean
+gitg_runner_get_preserve_line_endings (GitgRunner *runner)
+{
+	g_return_val_if_fail (GITG_IS_RUNNER (runner), FALSE);
+
+	return runner->priv->preserve_line_endings;
+}
+
 static gchar *
-gitg_strnchr (gchar *ptr, gssize size, gchar find)
+find_newline (gchar  *ptr,
+              gchar  *end,
+              gchar **line_end)
 {
-	while (size-- > 0)
+
+	while (ptr < end)
 	{
-		if (*ptr++ == find)
+		gunichar c;
+
+		c = g_utf8_get_char (ptr);
+
+		if (c == '\n')
+		{
+			/* That's it */
+			*line_end = g_utf8_next_char (ptr);
+			return ptr;
+		}
+		else if (c == '\r')
 		{
-			return ptr - 1;
+			gchar *next;
+
+			next = g_utf8_next_char (ptr);
+
+			if (next < end)
+			{
+				gunichar n = g_utf8_get_char (next);
+
+				if (n == '\n')
+				{
+					/* Consume both! */
+					*line_end = g_utf8_next_char (next);
+					return ptr;
+				}
+				else
+				{
+					/* That's it! */
+					*line_end = next;
+					return ptr;
+				}
+			}
+			else
+			{
+				/* Need to save it, it might come later... */
+				break;
+			}
 		}
+
+		ptr = g_utf8_next_char (ptr);
 	}
 
 	return NULL;
 }
 
 static void
-parse_lines (GitgRunner *runner, gchar *buffer, gssize size)
+parse_lines (GitgRunner *runner,
+             gchar      *buffer,
+             gssize      size)
 {
-	gchar *ptr = buffer;
+	gchar *ptr;
 	gchar *newline = NULL;
 	gint i = 0;
+	gchar *all;
+	gchar *end;
 
 	free_lines (runner);
 
-	while ((newline = gitg_strnchr (ptr, size, '\n')))
+	if (runner->priv->rest_buffer_size > 0)
 	{
-		gssize linesize = newline - ptr;
-		size -= linesize + 1;
-		*newline = '\0';
+		GString *str = g_string_sized_new (runner->priv->rest_buffer_size + size);
 
-		if (runner->priv->buffer)
-		{
-			gchar *buffered = g_strconcat (runner->priv->buffer, ptr, NULL);
-			g_free (runner->priv->buffer);
-			runner->priv->buffer = NULL;
+		g_string_append_len (str, runner->priv->rest_buffer, runner->priv->rest_buffer_size);
+		g_string_append_len (str, buffer, size);
 
-			runner->priv->lines[i++] = gitg_convert_utf8 (buffered, -1);
-			g_free (buffered);
-		}
-		else
-		{
-			runner->priv->lines[i++] = gitg_convert_utf8 (ptr, linesize);
-		}
+		all = g_string_free (str, FALSE);
+		size += runner->priv->rest_buffer_size;
 
-		ptr += linesize + 1;
+		g_free (runner->priv->rest_buffer);
+		runner->priv->rest_buffer = NULL;
+		runner->priv->rest_buffer_size = 0;
 	}
-
-	if (size)
+	else
 	{
-		gchar *tmp;
+		all = buffer;
+	}
+
+	ptr = all;
 
-		if (runner->priv->buffer != NULL)
+	gchar *line_end;
+	end = ptr + size;
+
+	while ((newline = find_newline (ptr, end, &line_end)))
+	{
+		if (runner->priv->preserve_line_endings)
 		{
-			tmp = g_strconcat (runner->priv->buffer, ptr, NULL);
+			runner->priv->lines[i++] = g_strndup (ptr, line_end - ptr);
 		}
 		else
 		{
-			tmp = g_strndup (ptr, size);
+			runner->priv->lines[i++] = g_strndup (ptr, newline - ptr);
 		}
 
-		g_free (runner->priv->buffer);
-		runner->priv->buffer = tmp;
+		ptr = line_end;
+	}
+
+	if (ptr < end)
+	{
+		runner->priv->rest_buffer_size = end - ptr;
+		runner->priv->rest_buffer = g_strndup (ptr, runner->priv->rest_buffer_size);
 	}
 
 	runner->priv->lines[i] = NULL;
 
 	g_signal_emit (runner, runner_signals[UPDATE], 0, runner->priv->lines);
+
+	if (all != buffer)
+	{
+		g_free (all);
+	}
 }
 
 static void
@@ -394,16 +495,41 @@ close_streams (GitgRunner *runner)
 		runner->priv->input_stream = NULL;
 	}
 
-	g_free (runner->priv->buffer);
-	runner->priv->buffer = NULL;
+	g_free (runner->priv->rest_buffer);
+	runner->priv->rest_buffer = NULL;
+	runner->priv->rest_buffer_size = 0;
+}
+
+static void
+emit_rest (GitgRunner *runner)
+{
+	if (runner->priv->rest_buffer_size > 0)
+	{
+		if (!runner->priv->preserve_line_endings &&
+		     runner->priv->rest_buffer[runner->priv->rest_buffer_size - 1] == '\r')
+		{
+			runner->priv->rest_buffer[runner->priv->rest_buffer_size - 1] = '\0';
+		}
+
+		gchar *b[] = {runner->priv->rest_buffer, NULL};
+
+		g_signal_emit (runner, runner_signals[UPDATE], 0, b);
+	}
 }
 
 static gboolean
-run_sync (GitgRunner *runner, gchar const *input, GError **error)
+run_sync (GitgRunner   *runner,
+          gchar const  *input,
+          GError      **error)
 {
 	if (input)
 	{
-		if (!g_output_stream_write_all (runner->priv->output_stream, input, strlen (input), NULL, NULL, error))
+		if (!g_output_stream_write_all (runner->priv->output_stream,
+		                                input,
+		                                strlen (input),
+		                                NULL,
+		                                NULL,
+		                                error))
 		{
 			runner_io_exit (runner->priv->pid, 1, runner);
 			close_streams (runner);
@@ -419,7 +545,12 @@ run_sync (GitgRunner *runner, gchar const *input, GError **error)
 
 	while (read == runner->priv->buffer_size)
 	{
-		if (!g_input_stream_read_all (runner->priv->input_stream, runner->priv->read_buffer, runner->priv->buffer_size, &read, NULL, error))
+		if (!g_input_stream_read_all (runner->priv->input_stream,
+		                              runner->priv->read_buffer,
+		                              runner->priv->buffer_size,
+		                              &read,
+		                              NULL,
+		                              error))
 		{
 			runner_io_exit (runner->priv->pid, 1, runner);
 			close_streams (runner);
@@ -432,8 +563,7 @@ run_sync (GitgRunner *runner, gchar const *input, GError **error)
 		parse_lines (runner, runner->priv->read_buffer, read);
 	}
 
-	gchar *b[] = {runner->priv->buffer, NULL};
-	g_signal_emit (runner, runner_signals[UPDATE], 0, b);
+	emit_rest (runner);
 
 	gint status = 0;
 	waitpid (runner->priv->pid, &status, 0);
@@ -468,7 +598,9 @@ async_failed (AsyncData *data)
 static void start_reading (GitgRunner *runner, AsyncData *data);
 
 static void
-read_output_ready (GInputStream *stream, GAsyncResult *result, AsyncData *data)
+read_output_ready (GInputStream *stream,
+                   GAsyncResult *result,
+                   AsyncData    *data)
 {
 	GError *error = NULL;
 
@@ -503,14 +635,7 @@ read_output_ready (GInputStream *stream, GAsyncResult *result, AsyncData *data)
 	if (read == 0)
 	{
 		/* End */
-		gchar *converted = gitg_convert_utf8 (data->runner->priv->buffer,
-		                                      -1);
-
-		gchar *b[] = {converted, NULL};
-
-		g_signal_emit (data->runner, runner_signals[UPDATE], 0, b);
-
-		g_free (converted);
+		emit_rest (data->runner);
 
 		gint status = 0;
 		waitpid (data->runner->priv->pid, &status, 0);
@@ -518,14 +643,19 @@ read_output_ready (GInputStream *stream, GAsyncResult *result, AsyncData *data)
 		runner_io_exit (data->runner->priv->pid, status, data->runner);
 		close_streams (data->runner);
 
-		g_signal_emit (data->runner, runner_signals[END_LOADING], 0, FALSE);
+		g_signal_emit (data->runner,
+		               runner_signals[END_LOADING],
+		               0,
+		               FALSE);
 
 		async_data_free (data);
 	}
 	else
 	{
 		data->runner->priv->read_buffer[read] = '\0';
-		parse_lines (data->runner, data->runner->priv->read_buffer, read);
+		parse_lines (data->runner,
+		             data->runner->priv->read_buffer,
+		             read);
 
 		if (g_cancellable_is_cancelled (data->cancellable))
 		{
@@ -539,9 +669,16 @@ read_output_ready (GInputStream *stream, GAsyncResult *result, AsyncData *data)
 }
 
 static void
-start_reading (GitgRunner *runner, AsyncData *data)
+start_reading (GitgRunner *runner,
+               AsyncData  *data)
 {
-	g_input_stream_read_async (runner->priv->input_stream, runner->priv->read_buffer, runner->priv->buffer_size, G_PRIORITY_DEFAULT, runner->priv->cancellable, (GAsyncReadyCallback)read_output_ready, data);
+	g_input_stream_read_async (runner->priv->input_stream,
+	                           runner->priv->read_buffer,
+	                           runner->priv->buffer_size,
+	                           G_PRIORITY_DEFAULT,
+	                           runner->priv->cancellable,
+	                           (GAsyncReadyCallback)read_output_ready,
+	                           data);
 }
 
 static void
@@ -572,11 +709,11 @@ write_input_ready (GOutputStream *stream, GAsyncResult *result, AsyncData *data)
 }
 
 static gboolean
-gitg_runner_run_streams (GitgRunner *runner,
-                         GInputStream *input_stream,
-                         GOutputStream *output_stream,
-                         gchar const *input,
-                         GError **error)
+gitg_runner_run_streams (GitgRunner     *runner,
+                         GInputStream   *input_stream,
+                         GOutputStream  *output_stream,
+                         gchar const    *input,
+                         GError        **error)
 {
 	gitg_runner_cancel (runner);
 
@@ -587,7 +724,14 @@ gitg_runner_run_streams (GitgRunner *runner,
 
 	if (input_stream)
 	{
-		runner->priv->input_stream = g_object_ref (input_stream);
+		GitgSmartCharsetConverter *smart;
+
+		smart = gitg_smart_charset_converter_new (gitg_encoding_get_candidates ());
+
+		runner->priv->input_stream = g_converter_input_stream_new (input_stream,
+		                                                           G_CONVERTER (smart));
+
+		g_object_unref (smart);
 	}
 
 	/* Emit begin-loading signal */
@@ -621,11 +765,11 @@ gitg_runner_run_streams (GitgRunner *runner,
 }
 
 gboolean
-gitg_runner_run_with_arguments (GitgRunner *runner,
-                                GFile *work_tree,
+gitg_runner_run_with_arguments (GitgRunner   *runner,
+                                GFile        *work_tree,
                                 gchar const **argv,
-                                gchar const *input,
-                                GError **error)
+                                gchar const  *input,
+                                GError      **error)
 {
 	g_return_val_if_fail (GITG_IS_RUNNER (runner), FALSE);
 
@@ -663,42 +807,45 @@ gitg_runner_run_with_arguments (GitgRunner *runner,
 		return FALSE;
 	}
 
-	GInputStream *input_stream = NULL;
 	GOutputStream *output_stream = NULL;
+	GInputStream *input_stream;
 
 	if (input)
 	{
-		output_stream = G_OUTPUT_STREAM (g_unix_output_stream_new (stdinf, TRUE));
+		output_stream = G_OUTPUT_STREAM (g_unix_output_stream_new (stdinf,
+		                                 TRUE));
 	}
 
 	input_stream = G_INPUT_STREAM (g_unix_input_stream_new (stdoutf, TRUE));
-	ret = gitg_runner_run_streams (runner, input_stream, output_stream, input, error);
+
+	ret = gitg_runner_run_streams (runner,
+	                               input_stream,
+	                               output_stream,
+	                               input,
+	                               error);
 
 	if (output_stream)
 	{
 		g_object_unref (output_stream);
 	}
 
-	if (input_stream)
-	{
-		g_object_unref (input_stream);
-	}
+	g_object_unref (input_stream);
 
 	return ret;
 }
 
 gboolean
-gitg_runner_run (GitgRunner *runner,
+gitg_runner_run (GitgRunner   *runner,
                  gchar const **argv,
-                 GError **error)
+                 GError      **error)
 {
 	return gitg_runner_run_with_arguments (runner, NULL, argv, NULL, error);
 }
 
 gboolean
-gitg_runner_run_stream (GitgRunner *runner,
-                        GInputStream *stream,
-                        GError **error)
+gitg_runner_run_stream (GitgRunner    *runner,
+                        GInputStream  *stream,
+                        GError       **error)
 {
 	return gitg_runner_run_streams (runner, stream, NULL, NULL, error);
 }
@@ -711,7 +858,9 @@ gitg_runner_get_buffer_size (GitgRunner *runner)
 }
 
 static void
-dummy_cb (GPid pid, gint status, gpointer data)
+dummy_cb (GPid     pid,
+          gint     status,
+          gpointer data)
 {
 }
 
@@ -756,7 +905,8 @@ gitg_runner_get_exit_status (GitgRunner *runner)
 }
 
 void
-gitg_runner_set_environment (GitgRunner *runner, gchar const **environment)
+gitg_runner_set_environment (GitgRunner   *runner,
+                             gchar const **environment)
 {
 	g_return_if_fail (GITG_IS_RUNNER (runner));
 
@@ -783,7 +933,9 @@ gitg_runner_set_environment (GitgRunner *runner, gchar const **environment)
 }
 
 void
-gitg_runner_add_environment (GitgRunner *runner, gchar const *key, gchar const *value)
+gitg_runner_add_environment (GitgRunner  *runner,
+                             gchar const *key,
+                             gchar const *value)
 {
 	g_return_if_fail (GITG_IS_RUNNER (runner));
 	g_return_if_fail (key != NULL);
@@ -794,11 +946,15 @@ gitg_runner_add_environment (GitgRunner *runner, gchar const *key, gchar const *
 		gchar **all = g_listenv ();
 
 		gint i = 0;
-		runner->priv->environment = g_malloc (sizeof (gchar *) * (g_strv_length (all) + 1));
+		runner->priv->environment = g_malloc (sizeof (gchar *) *
+		                                      (g_strv_length (all) + 1));
 
 		while (all && all[i])
 		{
-			runner->priv->environment[i] = g_strconcat (all[i], "=", g_getenv (all[i]), NULL);
+			runner->priv->environment[i] = g_strconcat (all[i],
+			                                            "=",
+			                                            g_getenv (all[i]),
+			                                            NULL);
 			++i;
 		}
 
diff --git a/libgitg/gitg-runner.h b/libgitg/gitg-runner.h
index 300919a..589ffe1 100644
--- a/libgitg/gitg-runner.h
+++ b/libgitg/gitg-runner.h
@@ -91,6 +91,11 @@ void gitg_runner_cancel (GitgRunner *runner);
 void gitg_runner_set_environment (GitgRunner *runner, gchar const **environment);
 void gitg_runner_add_environment (GitgRunner *runner, gchar const *key, gchar const *value);
 
+void gitg_runner_set_preserve_line_endings (GitgRunner *runner,
+                                            gboolean    preserve_line_endings);
+
+gboolean gitg_runner_get_preserve_line_endings (GitgRunner *runner);
+
 GQuark gitg_runner_error_quark (void);
 
 G_END_DECLS
diff --git a/libgitg/gitg-smart-charset-converter.c b/libgitg/gitg-smart-charset-converter.c
new file mode 100644
index 0000000..29cbad2
--- /dev/null
+++ b/libgitg/gitg-smart-charset-converter.c
@@ -0,0 +1,427 @@
+/*
+ * gedit-smart-charset-converter.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2009 - Ignacio Casal Quinteiro
+ *
+ * gedit is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gedit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, 
+ * Boston, MA  02110-1301  USA
+ */
+
+#include "gitg-smart-charset-converter.h"
+
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+
+#define GITG_SMART_CHARSET_CONVERTER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GITG_TYPE_SMART_CHARSET_CONVERTER, GitgSmartCharsetConverterPrivate))
+
+struct _GitgSmartCharsetConverterPrivate
+{
+	GCharsetConverter *charset_conv;
+
+	GSList *encodings;
+	GSList *current_encoding;
+
+	guint is_utf8 : 1;
+	guint use_first : 1;
+};
+
+static void gitg_smart_charset_converter_iface_init    (GConverterIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GitgSmartCharsetConverter, gitg_smart_charset_converter,
+			 G_TYPE_OBJECT,
+			 G_IMPLEMENT_INTERFACE (G_TYPE_CONVERTER,
+						gitg_smart_charset_converter_iface_init))
+
+GQuark
+gitg_charset_conversion_error_quark (void)
+{
+	static GQuark ret = 0;
+
+	if (G_UNLIKELY (ret == 0))
+	{
+		ret = g_quark_from_static_string ("GitgCharsetConversionError");
+	}
+
+	return ret;
+}
+
+static void
+gitg_smart_charset_converter_finalize (GObject *object)
+{
+	GitgSmartCharsetConverter *smart = GITG_SMART_CHARSET_CONVERTER (object);
+
+	g_slist_free (smart->priv->encodings);
+
+	G_OBJECT_CLASS (gitg_smart_charset_converter_parent_class)->finalize (object);
+}
+
+static void
+gitg_smart_charset_converter_dispose (GObject *object)
+{
+	GitgSmartCharsetConverter *smart = GITG_SMART_CHARSET_CONVERTER (object);
+
+	if (smart->priv->charset_conv != NULL)
+	{
+		g_object_unref (smart->priv->charset_conv);
+		smart->priv->charset_conv = NULL;
+	}
+
+	G_OBJECT_CLASS (gitg_smart_charset_converter_parent_class)->dispose (object);
+}
+
+static void
+gitg_smart_charset_converter_class_init (GitgSmartCharsetConverterClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->finalize = gitg_smart_charset_converter_finalize;
+	object_class->dispose = gitg_smart_charset_converter_dispose;
+
+	g_type_class_add_private (object_class, sizeof (GitgSmartCharsetConverterPrivate));
+}
+
+static void
+gitg_smart_charset_converter_init (GitgSmartCharsetConverter *smart)
+{
+	smart->priv = GITG_SMART_CHARSET_CONVERTER_GET_PRIVATE (smart);
+
+	smart->priv->charset_conv = NULL;
+	smart->priv->encodings = NULL;
+	smart->priv->current_encoding = NULL;
+	smart->priv->is_utf8 = FALSE;
+	smart->priv->use_first = FALSE;
+}
+
+static const GitgEncoding *
+get_encoding (GitgSmartCharsetConverter *smart)
+{
+	if (smart->priv->current_encoding == NULL)
+	{
+		smart->priv->current_encoding = smart->priv->encodings;
+	}
+	else
+	{
+		smart->priv->current_encoding = g_slist_next (smart->priv->current_encoding);
+	}
+
+	if (smart->priv->current_encoding != NULL)
+		return (const GitgEncoding *)smart->priv->current_encoding->data;
+
+#if 0
+	FIXME: uncomment this when using fallback
+	/* If we tried all encodings, we return the first encoding */
+	smart->priv->use_first = TRUE;
+	smart->priv->current_encoding = smart->priv->encodings;
+
+	return (const GitgEncoding *)smart->priv->current_encoding->data;
+#endif
+	return NULL;
+}
+
+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 (GitgSmartCharsetConverter *smart,
+		const void                 *inbuf,
+		gsize                       inbuf_size)
+{
+	GCharsetConverter *conv = NULL;
+
+	if (inbuf == NULL || inbuf_size == 0)
+	{
+		smart->priv->is_utf8 = TRUE;
+		return NULL;
+	}
+
+	if (smart->priv->encodings != NULL &&
+	    smart->priv->encodings->next == NULL)
+	{
+		smart->priv->use_first = TRUE;
+	}
+
+	/* We just check the first block */
+	while (TRUE)
+	{
+		const GitgEncoding *enc;
+
+		if (conv != NULL)
+		{
+			g_object_unref (conv);
+			conv = NULL;
+		}
+
+		/* We get an encoding from the list */
+		enc = get_encoding (smart);
+
+		/* if it is NULL we didn't guess anything */
+		if (enc == NULL)
+		{
+			break;
+		}
+
+		if (enc == gitg_encoding_get_utf8 ())
+		{
+			gsize remainder;
+			const gchar *end;
+			
+			if (g_utf8_validate (inbuf, inbuf_size, &end) ||
+			    smart->priv->use_first)
+			{
+				smart->priv->is_utf8 = TRUE;
+				break;
+			}
+
+			/* Check if the end is less than one char */
+			remainder = inbuf_size - (end - (gchar *)inbuf);
+			if (remainder < 6)
+			{
+				smart->priv->is_utf8 = TRUE;
+				break;
+			}
+
+			continue;
+		}
+
+		conv = g_charset_converter_new ("UTF-8",
+						gitg_encoding_get_charset (enc),
+						NULL);
+
+		/* If we tried all encodings we use the first one */
+		if (smart->priv->use_first)
+		{
+			break;
+		}
+
+		/* Try to convert */
+		if (try_convert (conv, inbuf, inbuf_size))
+		{
+			break;
+		}
+	}
+
+	if (conv != NULL)
+	{
+		g_converter_reset (G_CONVERTER (conv));
+
+		/* FIXME: uncomment this when we want to use the fallback
+		g_charset_converter_set_use_fallback (conv, TRUE);*/
+	}
+
+	return conv;
+}
+
+static GConverterResult
+gitg_smart_charset_converter_convert (GConverter       *converter,
+				       const void       *inbuf,
+				       gsize             inbuf_size,
+				       void             *outbuf,
+				       gsize             outbuf_size,
+				       GConverterFlags   flags,
+				       gsize            *bytes_read,
+				       gsize            *bytes_written,
+				       GError          **error)
+{
+	GitgSmartCharsetConverter *smart = GITG_SMART_CHARSET_CONVERTER (converter);
+
+	/* Guess the encoding if we didn't make it yet */
+	if (smart->priv->charset_conv == NULL &&
+	    !smart->priv->is_utf8)
+	{
+		smart->priv->charset_conv = guess_encoding (smart, inbuf, inbuf_size);
+
+		/* If we still have the previous case is that we didn't guess
+		   anything */
+		if (smart->priv->charset_conv == NULL &&
+		    !smart->priv->is_utf8)
+		{
+			g_set_error_literal (error, GITG_CHARSET_CONVERSION_ERROR,
+					     GITG_CHARSET_CONVERSION_ERROR_ENCODING_AUTO_DETECTION_FAILED,
+					     _("It is not possible to detect the encoding automatically"));
+
+			return G_CONVERTER_ERROR;
+		}
+	}
+
+	/* Now if the encoding is utf8 just redirect the input to the output */
+	if (smart->priv->is_utf8)
+	{
+		gsize size;
+		GConverterResult ret;
+
+		size = MIN (inbuf_size, outbuf_size);
+
+		memcpy (outbuf, inbuf, size);
+		*bytes_read = size;
+		*bytes_written = size;
+
+		ret = G_CONVERTER_CONVERTED;
+
+		if (flags & G_CONVERTER_INPUT_AT_END)
+			ret = G_CONVERTER_FINISHED;
+		else if (flags & G_CONVERTER_FLUSH)
+			ret = G_CONVERTER_FLUSHED;
+
+		return ret;
+	}
+
+	/* If we reached here is because we need to convert the text so, we
+	   convert it with the charset converter */
+	return g_converter_convert (G_CONVERTER (smart->priv->charset_conv),
+				    inbuf,
+				    inbuf_size,
+				    outbuf,
+				    outbuf_size,
+				    flags,
+				    bytes_read,
+				    bytes_written,
+				    error);
+}
+
+static void
+gitg_smart_charset_converter_reset (GConverter *converter)
+{
+	GitgSmartCharsetConverter *smart = GITG_SMART_CHARSET_CONVERTER (converter);
+
+	smart->priv->current_encoding = NULL;
+	smart->priv->is_utf8 = FALSE;
+
+	if (smart->priv->charset_conv != NULL)
+	{
+		g_object_unref (smart->priv->charset_conv);
+		smart->priv->charset_conv = NULL;
+	}
+}
+
+static void
+gitg_smart_charset_converter_iface_init (GConverterIface *iface)
+{
+	iface->convert = gitg_smart_charset_converter_convert;
+	iface->reset = gitg_smart_charset_converter_reset;
+}
+
+GitgSmartCharsetConverter *
+gitg_smart_charset_converter_new (GSList *candidate_encodings)
+{
+	GitgSmartCharsetConverter *smart;
+
+	g_return_val_if_fail (candidate_encodings != NULL, NULL);
+
+	smart = g_object_new (GITG_TYPE_SMART_CHARSET_CONVERTER, NULL);
+
+	smart->priv->encodings = g_slist_copy (candidate_encodings);
+
+	return smart;
+}
+
+const GitgEncoding *
+gitg_smart_charset_converter_get_guessed (GitgSmartCharsetConverter *smart)
+{
+	g_return_val_if_fail (GITG_IS_SMART_CHARSET_CONVERTER (smart), NULL);
+
+	if (smart->priv->current_encoding != NULL)
+	{
+		return (const GitgEncoding *)smart->priv->current_encoding->data;
+	}
+	else if (smart->priv->is_utf8)
+	{
+		return gitg_encoding_get_utf8 ();
+	}
+
+	return NULL;
+}
+
+guint
+gitg_smart_charset_converter_get_num_fallbacks (GitgSmartCharsetConverter *smart)
+{
+	g_return_val_if_fail (GITG_IS_SMART_CHARSET_CONVERTER (smart), FALSE);
+
+	if (smart->priv->charset_conv == NULL)
+		return FALSE;
+
+	return g_charset_converter_get_num_fallbacks (smart->priv->charset_conv) != 0;
+}
+
+/* ex:ts=8:noet: */
diff --git a/libgitg/gitg-smart-charset-converter.h b/libgitg/gitg-smart-charset-converter.h
new file mode 100644
index 0000000..64d7aff
--- /dev/null
+++ b/libgitg/gitg-smart-charset-converter.h
@@ -0,0 +1,76 @@
+/*
+ * gedit-smart-charset-converter.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2009 - Ignacio Casal Quinteiro
+ *
+ * gedit is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gedit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, 
+ * Boston, MA  02110-1301  USA
+ */
+
+#ifndef __GITG_SMART_CHARSET_CONVERTER_H__
+#define __GITG_SMART_CHARSET_CONVERTER_H__
+
+#include <glib-object.h>
+
+#include "gitg-encodings.h"
+
+G_BEGIN_DECLS
+
+#define GITG_TYPE_SMART_CHARSET_CONVERTER		(gitg_smart_charset_converter_get_type ())
+#define GITG_SMART_CHARSET_CONVERTER(obj)		(G_TYPE_CHECK_INSTANCE_CAST ((obj), GITG_TYPE_SMART_CHARSET_CONVERTER, GitgSmartCharsetConverter))
+#define GITG_SMART_CHARSET_CONVERTER_CONST(obj)	(G_TYPE_CHECK_INSTANCE_CAST ((obj), GITG_TYPE_SMART_CHARSET_CONVERTER, GitgSmartCharsetConverter const))
+#define GITG_SMART_CHARSET_CONVERTER_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST ((klass), GITG_TYPE_SMART_CHARSET_CONVERTER, GitgSmartCharsetConverterClass))
+#define GITG_IS_SMART_CHARSET_CONVERTER(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), GITG_TYPE_SMART_CHARSET_CONVERTER))
+#define GITG_IS_SMART_CHARSET_CONVERTER_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((klass), GITG_TYPE_SMART_CHARSET_CONVERTER))
+#define GITG_SMART_CHARSET_CONVERTER_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS ((obj), GITG_TYPE_SMART_CHARSET_CONVERTER, GitgSmartCharsetConverterClass))
+
+typedef struct _GitgSmartCharsetConverter		GitgSmartCharsetConverter;
+typedef struct _GitgSmartCharsetConverterClass		GitgSmartCharsetConverterClass;
+typedef struct _GitgSmartCharsetConverterPrivate	GitgSmartCharsetConverterPrivate;
+
+#define GITG_CHARSET_CONVERSION_ERROR (gitg_charset_conversion_error_quark ())
+
+typedef enum
+{
+	GITG_CHARSET_CONVERSION_ERROR_ENCODING_AUTO_DETECTION_FAILED
+} GitgCharserConversionError;
+
+struct _GitgSmartCharsetConverter
+{
+	GObject parent;
+	
+	GitgSmartCharsetConverterPrivate *priv;
+};
+
+struct _GitgSmartCharsetConverterClass
+{
+	GObjectClass parent_class;
+};
+
+GType gitg_smart_charset_converter_get_type (void) G_GNUC_CONST;
+GQuark gitg_charset_conversion_error_quark (void);
+
+GitgSmartCharsetConverter	*gitg_smart_charset_converter_new		(GSList *candidate_encodings);
+
+const GitgEncoding		*gitg_smart_charset_converter_get_guessed	(GitgSmartCharsetConverter *smart);
+
+guint				 gitg_smart_charset_converter_get_num_fallbacks(GitgSmartCharsetConverter *smart);
+
+G_END_DECLS
+
+#endif /* __GITG_SMART_CHARSET_CONVERTER_H__ */
+
+/* ex:ts=8:noet: */



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