[gitg] Improved staging/unstaging



commit 3ac8587189b6ac5698939e17ac3dd59619c7c523
Author: Jesse van den Kieboom <jessevdk gnome org>
Date:   Sun May 30 10:50:36 2010 +0200

    Improved staging/unstaging
    
    Per-line staging/unstaging is now possible.

 data/gitgstyle.xml             |    1 +
 gitg/gitg-cell-renderer-path.c |    2 +-
 gitg/gitg-commit-view.c        |  596 +++++++++++++++++++++++++++++++++++-----
 gitg/gitg-diff-line-renderer.c |  195 ++++++++++++-
 gitg/gitg-diff-view.c          |  438 +++++++++++++++++++++++++++---
 gitg/gitg-diff-view.h          |   19 ++
 gitg/gitg-label-renderer.c     |   25 +--
 gitg/gitg-utils.c              |   76 +++++
 gitg/gitg-utils.h              |    4 +
 9 files changed, 1227 insertions(+), 129 deletions(-)
---
diff --git a/data/gitgstyle.xml b/data/gitgstyle.xml
index 74dd877..3c750e7 100644
--- a/data/gitgstyle.xml
+++ b/data/gitgstyle.xml
@@ -60,6 +60,7 @@
   <!-- Default -->
   <style name="gitgdiff:text"               foreground="skyblue3" background="aluminium1"/>
   <style name="text"                        use-style="gitgdiff:text"/>
+  <style name="current-line"                line-background="aluminium4" foreground="aluminium1"/>
 
   <!-- Right Margin -->
   <style name="right-margin"                foreground="aluminium5" background="aluminium4"/>
diff --git a/gitg/gitg-cell-renderer-path.c b/gitg/gitg-cell-renderer-path.c
index 449a936..3baf088 100644
--- a/gitg/gitg-cell-renderer-path.c
+++ b/gitg/gitg-cell-renderer-path.c
@@ -325,7 +325,7 @@ renderer_render(GtkCellRenderer *renderer, GdkDrawable *window, GtkWidget *widge
 
 	cairo_t *cr = gdk_cairo_create(window);
 
-	cairo_rectangle(cr, area->x, area->y, area->width, area->height);
+	gdk_cairo_rectangle (cr, area);
 	cairo_clip(cr);
 
 	draw_paths(self, cr, area);
diff --git a/gitg/gitg-commit-view.c b/gitg/gitg-commit-view.c
index 173f694..06e819f 100644
--- a/gitg/gitg-commit-view.c
+++ b/gitg/gitg-commit-view.c
@@ -90,6 +90,8 @@ struct _GitgCommitViewPrivate
 	GtkTextIter context_iter;
 
 	GtkActionGroup *group_context;
+	GtkTextMark *highlight_mark;
+	GtkTextTag *highlight_tag;
 };
 
 static void gitg_commit_view_buildable_iface_init(GtkBuildableIface *iface);
@@ -129,20 +131,22 @@ static void on_edit_file(GtkAction *action, GitgCommitView *view);
 static void on_check_button_amend_toggled (GtkToggleButton *button, GitgCommitView *view);
 
 static void
-gitg_commit_view_finalize(GObject *object)
+gitg_commit_view_finalize (GObject *object)
 {
-	GitgCommitView *view = GITG_COMMIT_VIEW(object);
+	GitgCommitView *view = GITG_COMMIT_VIEW (object);
 
 	if (view->priv->update_id)
-		g_signal_handler_disconnect(view->priv->runner, view->priv->update_id);
+	{
+		g_signal_handler_disconnect (view->priv->runner, view->priv->update_id);
+	}
 
-	gitg_runner_cancel(view->priv->runner);
-	g_object_unref(view->priv->runner);
-	g_object_unref(view->priv->ui_manager);
+	gitg_runner_cancel (view->priv->runner);
+	g_object_unref (view->priv->runner);
+	g_object_unref (view->priv->ui_manager);
 
-	gdk_cursor_unref(view->priv->hand);
+	gdk_cursor_unref (view->priv->hand);
 
-	G_OBJECT_CLASS(gitg_commit_view_parent_class)->finalize(object);
+	G_OBJECT_CLASS (gitg_commit_view_parent_class)->finalize (object);
 }
 
 static void
@@ -672,28 +676,217 @@ get_hunk_patch(GitgCommitView *view, GtkTextIter *iter)
 	return g_strconcat(header, contents, NULL);
 }
 
+static gchar *
+line_patch_contents (GitgCommitView *view,
+                     GtkTextIter const *iter,
+                     GitgDiffLineType old_type,
+                     GitgDiffLineType new_type)
+{
+	GtkTextIter start;
+	GtkTextIter end;
+	GtkTextIter patch_iter = *iter;
+	GitgDiffView *diff_view = GITG_DIFF_VIEW (view->priv->changes_view);
+	GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (diff_view));
+	GitgDiffIter diff_iter;
+
+	gtk_text_iter_set_line_offset (&patch_iter, 0);
+
+	if (!gitg_diff_view_get_hunk_at_iter (diff_view, &patch_iter, &diff_iter))
+	{
+		return NULL;
+	}
+
+	gitg_diff_iter_get_bounds (&diff_iter, &start, &end);
+	GtkTextIter begin = start;
+
+	GString *patch = g_string_new ("");
+	gchar *header = NULL;
+	gint count_old = 0;
+	gint count_new = 0;
+
+	while (gtk_text_iter_compare (&start, &end) < 0)
+	{
+		GitgDiffLineType line_type;
+		gboolean is_patch_line;
+
+		line_type = gitg_diff_view_get_line_type (diff_view, &start);
+
+		is_patch_line = gtk_text_iter_equal (&start, &patch_iter);
+
+		gchar *text;
+		GtkTextIter line_end = start;
+		gtk_text_iter_forward_to_line_end (&line_end);
+
+		if (gtk_text_iter_equal (&start, &begin))
+		{
+			header = gtk_text_buffer_get_text (buffer, &start, &line_end, TRUE);
+		}
+		else
+		{
+			if (line_type == old_type && !is_patch_line)
+			{
+				/* Take over like it was context */
+				g_string_append_c (patch, ' ');
+
+				if (!gtk_text_iter_ends_line (&start))
+				{
+					gtk_text_iter_forward_char (&start);
+				}
+			}
+
+			if (line_type == GITG_DIFF_LINE_TYPE_NONE ||
+			    line_type == old_type || is_patch_line)
+			{
+				/* copy context */
+				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)
+				{
+					++count_old;
+				}
+
+				if (!is_patch_line || line_type == GITG_DIFF_LINE_TYPE_ADD)
+				{
+					++count_new;
+				}
+
+				g_free (text);
+			}
+		}
+
+		if (!gtk_text_iter_forward_line (&start))
+		{
+			break;
+		}
+	}
+
+	gchar *head = gitg_utils_rewrite_hunk_counters (header, count_old, count_new);
+	g_free (header);
+	gchar *ret = NULL;
+
+	if (head)
+	{
+		gchar *contents = g_string_free (patch, FALSE);
+		ret = g_strconcat (head, "\n", contents, NULL);
+
+		g_free (contents);
+		g_free (head);
+	}
+
+	return ret;
+}
+
+static gboolean
+stage_unstage_hunk (GitgCommitView *view, gchar const *hunk, GError **error)
+{
+	gboolean ret;
+	gboolean unstage = view->priv->current_changes & GITG_CHANGED_FILE_CHANGES_UNSTAGED;
+
+	if (unstage)
+	{
+		ret = gitg_commit_stage (view->priv->commit, view->priv->current_file, hunk, error);
+	}
+	else
+	{
+		ret = gitg_commit_unstage (view->priv->commit, view->priv->current_file, hunk, error);
+	}
+
+	return ret;
+}
+
+static gboolean
+handle_stage_unstage_line (GitgCommitView *view, GtkTextIter const *iter)
+{
+	GitgDiffView *diff_view = GITG_DIFF_VIEW (view->priv->changes_view);
+	GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view->priv->changes_view));
+	gchar *header = get_patch_header (view, buffer, iter);
+	GitgDiffLineType old_type;
+	GitgDiffLineType new_type;
+
+	if (!header)
+	{
+		return FALSE;
+	}
+
+	GitgDiffIter diff_iter;
+
+	if (!gitg_diff_view_get_header_at_iter (diff_view, iter, &diff_iter))
+	{
+		g_free (header);
+		return FALSE;
+	}
+
+	if (view->priv->current_changes & GITG_CHANGED_FILE_CHANGES_UNSTAGED)
+	{
+		old_type = GITG_DIFF_LINE_TYPE_REMOVE;
+		new_type = GITG_DIFF_LINE_TYPE_ADD;
+	}
+	else
+	{
+		old_type = GITG_DIFF_LINE_TYPE_ADD;
+		new_type = GITG_DIFF_LINE_TYPE_REMOVE;
+	}
+
+	gchar *contents = line_patch_contents (view, iter, old_type, new_type);
+
+	if (!contents)
+	{
+		g_free (header);
+		return FALSE;
+	}
+
+	gboolean ret;
+	GitgChangedFile *file = g_object_ref (view->priv->current_file);
+	GError *error = NULL;
+	gchar *hunk = g_strconcat (header, contents, NULL);
+
+	g_free (contents);
+	g_free (header);
+
+	ret = stage_unstage_hunk (view, hunk, &error);
+
+	if (ret && file == view->priv->current_file)
+	{
+		gitg_diff_view_clear_line (GITG_DIFF_VIEW (view->priv->changes_view),
+		                           iter,
+		                           old_type,
+		                           new_type);
+	}
+	else if (!ret)
+	{
+		g_warning ("Could not stage/unstage: %s", error->message);
+		g_error_free (error);
+	}
+
+	g_free (hunk);
+	g_object_unref (file);
+
+	return ret;
+}
+
 static gboolean
 handle_stage_unstage(GitgCommitView *view, GtkTextIter *iter)
 {
-	gchar *hunk = get_hunk_patch(view, iter);
+	gchar *hunk = get_hunk_patch (view, iter);
 
 	if (!hunk)
+	{
 		return FALSE;
+	}
 
 	gboolean ret;
-	GitgChangedFile *file = g_object_ref(view->priv->current_file);
-	gboolean unstage = view->priv->current_changes & GITG_CHANGED_FILE_CHANGES_UNSTAGED;
+	GitgChangedFile *file = g_object_ref (view->priv->current_file);
 	GError *error = NULL;
 
-	if (unstage)
-		ret = gitg_commit_stage(view->priv->commit, view->priv->current_file, hunk, &error);
-	else
-		ret = gitg_commit_unstage(view->priv->commit, view->priv->current_file, hunk, &error);
+	ret = stage_unstage_hunk (view, hunk, &error);
 
 	if (ret && file == view->priv->current_file)
 	{
 		/* remove hunk from text view */
-		gitg_diff_view_remove_hunk(GITG_DIFF_VIEW(view->priv->changes_view), iter);
+		gitg_diff_view_remove_hunk (GITG_DIFF_VIEW (view->priv->changes_view), iter);
 	}
 	else if (!ret)
 	{
@@ -701,76 +894,273 @@ handle_stage_unstage(GitgCommitView *view, GtkTextIter *iter)
 		g_error_free (error);
 	}
 
-	g_object_unref(file);
-	g_free(hunk);
+	g_object_unref (file);
+	g_free (hunk);
 
 	return ret;
 }
 
 static gboolean
-get_hunk_at_pointer(GitgCommitView *view, GtkTextIter *iter, gchar **hunk)
+get_info_at_pointer (GitgCommitView *view,
+                     GtkTextIter *iter,
+                     gboolean *is_hunk,
+                     gchar **hunk,
+                     GitgDiffLineType *line_type)
 {
-	GtkTextView *textview = GTK_TEXT_VIEW(view->priv->changes_view);
+	GtkTextView *textview = GTK_TEXT_VIEW (view->priv->changes_view);
 	gint x;
 	gint y;
+	gint width;
+	gint height;
 	gint buf_x;
 	gint buf_y;
 
 	/* Get where the pointer really is. */
-	GdkWindow *win = gtk_text_view_get_window(textview, GTK_TEXT_WINDOW_TEXT);
-	gdk_window_get_pointer(win, &x, &y, NULL);
+	GdkWindow *win = gtk_text_view_get_window (textview, GTK_TEXT_WINDOW_TEXT);
+
+	gdk_window_get_pointer (win, &x, &y, NULL);
+	gdk_drawable_get_size (GDK_DRAWABLE (win), &width, &height);
+
+	if (x < 0 || y < 0 || x > width || y > height)
+	{
+		return FALSE;
+	}
 
 	/* Get the iter where the cursor is at */
-	gtk_text_view_window_to_buffer_coords(textview, GTK_TEXT_WINDOW_TEXT, x, y, &buf_x, &buf_y);
-	gtk_text_view_get_iter_at_location(textview, iter, buf_x, buf_y);
+	gtk_text_view_window_to_buffer_coords (textview, GTK_TEXT_WINDOW_TEXT, x, y, &buf_x, &buf_y);
+	gtk_text_view_get_iter_at_location (textview, iter, buf_x, buf_y);
 
-	if (gtk_text_iter_backward_line(iter))
-		gtk_text_iter_forward_line(iter);
+	gtk_text_iter_set_line_offset (iter, 0);
 
-	GtkSourceBuffer *buffer = GTK_SOURCE_BUFFER(gtk_text_view_get_buffer(textview));
+	GtkSourceBuffer *buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (textview));
 
-	if (!has_hunk_mark(buffer, iter))
-		return FALSE;
+	if (is_hunk)
+	{
+		*is_hunk = has_hunk_mark (buffer, iter);
+
+		if (*is_hunk && hunk)
+		{
+			*hunk = get_hunk_patch (view, iter);
+		}
+	}
 
-	if (hunk)
-		*hunk = get_hunk_patch(view, iter);
+	if (line_type)
+	{
+		*line_type = gitg_diff_view_get_line_type (GITG_DIFF_VIEW (view->priv->changes_view),
+		                                           iter);
+	}
 
 	return TRUE;
 }
 
-static gboolean
-view_event(GtkWidget *widget, GdkEventAny *event, GitgCommitView *view)
+static void
+unset_highlight (GitgCommitView *view)
 {
-	GtkTextWindowType type;
+	if (!view->priv->highlight_mark)
+	{
+		return;
+	}
+
+	GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view->priv->changes_view));
+	GtkTextIter start;
+	GtkTextIter end;
+
+	gtk_text_buffer_get_iter_at_mark (buffer, &start, view->priv->highlight_mark);
+	end = start;
+
+	gtk_text_iter_forward_to_line_end (&end);
+
+	gtk_text_buffer_remove_tag (buffer, view->priv->highlight_tag, &start, &end);
+
+	gtk_text_buffer_delete_mark (buffer, view->priv->highlight_mark);
+	view->priv->highlight_mark = NULL;
+}
+
+static void
+set_highlight (GitgCommitView *view, GtkTextIter *iter)
+{
+	if (!view->priv->highlight_tag)
+	{
+		return;
+	}
+
+	GtkTextIter start = *iter;
+	GtkTextBuffer *buffer = gtk_text_iter_get_buffer (iter);
+
+	gtk_text_iter_set_line_offset (&start, 0);
+
+	if (view->priv->highlight_mark != NULL)
+	{
+		GtkTextIter mark_iter;
+		gtk_text_buffer_get_iter_at_mark (buffer,
+		                                  &mark_iter,
+		                                  view->priv->highlight_mark);
+
+		if (gtk_text_iter_equal (&start, &mark_iter))
+		{
+			return;
+		}
+
+		unset_highlight (view);
+	}
+
+	view->priv->highlight_mark = gtk_text_buffer_create_mark (buffer,
+	                                                          NULL,
+	                                                          &start,
+	                                                          TRUE);
+
+	GtkTextIter end = start;
+	gtk_text_iter_forward_to_line_end (&end);
+
+	gtk_text_buffer_apply_tag (buffer,
+	                           view->priv->highlight_tag,
+	                           &start,
+	                           &end);
+}
+
+static void
+update_cursor_view (GitgCommitView *view)
+{
+	gboolean is_hunk = FALSE;
 	GtkTextIter iter;
+	GitgDiffLineType line_type = GITG_DIFF_LINE_TYPE_NONE;
+	GdkWindow *window;
 
-	type = gtk_text_view_get_window_type(GTK_TEXT_VIEW(widget), event->window);
+	window = gtk_text_view_get_window (GTK_TEXT_VIEW (view->priv->changes_view), GTK_TEXT_WINDOW_TEXT);
 
-	if (type != GTK_TEXT_WINDOW_TEXT)
-		return FALSE;
+	if (!get_info_at_pointer (view, &iter, &is_hunk, NULL, &line_type))
+	{
+		unset_highlight (view);
+		gdk_window_set_cursor (window, NULL);
+		return;
+	}
 
-	if (event->type != GDK_MOTION_NOTIFY && event->type != GDK_BUTTON_PRESS)
+	if (is_hunk ||
+	    line_type == GITG_DIFF_LINE_TYPE_ADD ||
+	    line_type == GITG_DIFF_LINE_TYPE_REMOVE)
+	{
+		gdk_window_set_cursor (window, view->priv->hand);
+		set_highlight (view, &iter);
+	}
+	else
+	{
+		gdk_window_set_cursor (window, NULL);
+		unset_highlight (view);
+	}
+}
+
+static gboolean
+view_event (GtkWidget *widget, GdkEventAny *event, GitgCommitView *view)
+{
+	GtkTextWindowType type;
+	GtkTextBuffer *buffer;
+
+	type = gtk_text_view_get_window_type (GTK_TEXT_VIEW (widget), event->window);
+	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+
+	if (type == GTK_TEXT_WINDOW_TEXT && event->type == GDK_LEAVE_NOTIFY)
+	{
+		unset_highlight (view);
+		gdk_window_set_cursor (event->window, NULL);
 		return FALSE;
+	}
 
-	gboolean is_hunk = get_hunk_at_pointer(view, &iter, NULL);
+	if (event->type == GDK_LEAVE_NOTIFY ||
+	    event->type == GDK_MOTION_NOTIFY ||
+	    event->type == GDK_BUTTON_PRESS ||
+	    event->type == GDK_BUTTON_RELEASE ||
+	    event->type == GDK_ENTER_NOTIFY)
+	{
+		update_cursor_view (view);
+	}
 
-	if (event->type == GDK_MOTION_NOTIFY)
+	if (type == GTK_TEXT_WINDOW_TEXT && event->type == GDK_BUTTON_RELEASE &&
+	    ((GdkEventButton *)event)->button == 1 &&
+	    !gtk_text_buffer_get_has_selection (buffer))
 	{
+		GtkTextIter iter;
+		gboolean is_hunk = FALSE;
+		GitgDiffLineType line_type = GITG_DIFF_LINE_TYPE_NONE;
+
+		get_info_at_pointer (view, &iter, &is_hunk, NULL, &line_type);
+
 		if (is_hunk)
 		{
-			gdk_window_set_cursor(event->window, view->priv->hand);
-		} 
-		else
+			if (handle_stage_unstage (view, &iter))
+			{
+				unset_highlight (view);
+				update_cursor_view (view);
+			}
+		}
+		else if (line_type == GITG_DIFF_LINE_TYPE_ADD ||
+		         line_type == GITG_DIFF_LINE_TYPE_REMOVE)
 		{
-			gdk_window_set_cursor(event->window, NULL);
+			if (handle_stage_unstage_line (view, &iter))
+			{
+				unset_highlight (view);
+				update_cursor_view (view);
+			}
 		}
 	}
-	else if (is_hunk && ((GdkEventButton *)event)->button == 1)
+
+	return FALSE;
+}
+
+static gchar *
+stage_unstage_label_func (GitgDiffView   *diff_view,
+                          gint            line,
+                          GitgCommitView *view)
+{
+	static gchar const *format = "<small><b>%s</b></small>";
+
+	if (line == -1)
 	{
-		handle_stage_unstage(view, &iter);
+		static gchar const *longest_label = NULL;
+
+		gchar const *stage = _("stage");
+		gchar const *unstage = _("unstage");
+
+		if (!longest_label)
+		{
+			if (g_utf8_strlen (stage, -1) > g_utf8_strlen (unstage, -1))
+			{
+				longest_label = stage;
+			}
+			else
+			{
+				longest_label = unstage;
+			}
+		}
+
+		return g_markup_printf_escaped (format, _("stage"));
 	}
+	else if (view->priv->highlight_mark)
+	{
+		GtkTextBuffer *buffer;
+		GtkTextIter iter;
+		GtkTextIter hl_iter;
 
-	return FALSE;
+		buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (diff_view));
+		gtk_text_buffer_get_iter_at_line (buffer, &iter, line);
+
+		gtk_text_buffer_get_iter_at_mark (buffer,
+		                                  &hl_iter,
+		                                  view->priv->highlight_mark);
+
+		if (gtk_text_iter_equal (&iter, &hl_iter))
+		{
+			if (view->priv->current_changes & GITG_CHANGED_FILE_CHANGES_UNSTAGED)
+			{
+				return g_markup_printf_escaped (format, _("stage"));
+			}
+			else
+			{
+				return g_markup_printf_escaped (format, _("unstage"));
+			}
+		}
+	}
+
+	return NULL;
 }
 
 static GtkTextBuffer *
@@ -922,6 +1312,15 @@ initialize_dnd_unstaged(GitgCommitView *view)
 }
 
 static void
+on_tag_added (GtkTextTagTable *table,
+              GtkTextTag *tag,
+              GitgCommitView *view)
+{
+	gtk_text_tag_set_priority (view->priv->highlight_tag,
+	                           gtk_text_tag_table_get_size (table) - 1);
+}
+
+static void
 gitg_commit_view_parser_finished(GtkBuildable *buildable, GtkBuilder *builder)
 {
 	if (parent_iface.parser_finished)
@@ -953,6 +1352,15 @@ gitg_commit_view_parser_finished(GtkBuildable *buildable, GtkBuilder *builder)
 	set_sort_func(self->priv->store_staged);
 
 	self->priv->changes_view = GTK_SOURCE_VIEW(gtk_builder_get_object(builder, "source_view_changes"));
+
+	gtk_widget_add_events (GTK_WIDGET (self->priv->changes_view),
+	                       GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK);
+
+	gitg_diff_view_set_label_func (GITG_DIFF_VIEW (self->priv->changes_view),
+	                               (GitgDiffViewLabelFunc)stage_unstage_label_func,
+	                               self,
+	                               NULL);
+
 	self->priv->comment_view = GTK_TEXT_VIEW(gtk_builder_get_object(builder, "text_view_comment"));
 	self->priv->check_button_signed_off_by = GTK_CHECK_BUTTON(gtk_builder_get_object(builder, "check_button_signed_off_by"));
 	self->priv->check_button_amend = GTK_CHECK_BUTTON(gtk_builder_get_object(builder, "check_button_amend"));
@@ -1000,6 +1408,63 @@ gitg_commit_view_parser_finished(GtkBuildable *buildable, GtkBuilder *builder)
 	set_icon_data_func(self, self->priv->tree_view_unstaged, GTK_CELL_RENDERER(gtk_builder_get_object(builder, "unstaged_cell_renderer_icon")));
 	set_icon_data_func(self, self->priv->tree_view_staged, GTK_CELL_RENDERER(gtk_builder_get_object(builder, "staged_cell_renderer_icon")));
 
+	GtkSourceStyleScheme *scheme;
+	GtkSourceStyle *style;
+
+	scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (buffer));
+	style = gtk_source_style_scheme_get_style (scheme, "current-line");
+
+	gchar *background = NULL;
+	gboolean background_set = FALSE;
+
+	gchar *foreground = NULL;
+	gboolean foreground_set = FALSE;
+
+	if (style)
+	{
+		g_object_get (style,
+		              "line-background",
+		              &background,
+		              "line-background-set",
+		              &background_set,
+		              "foreground",
+		              &foreground,
+		              "foreground-set",
+		              &foreground_set,
+		              NULL);
+
+		if (!background_set)
+		{
+			g_object_get (style,
+			              "background",
+			              &background,
+			              "background-set",
+			              &background_set,
+			              NULL);
+		}
+	}
+
+	if (background_set)
+	{
+		self->priv->highlight_tag = gtk_text_buffer_create_tag (buffer,
+		                                                        NULL,
+		                                                        "paragraph-background",
+		                                                        background,
+		                                                        "foreground",
+		                                                        foreground,
+		                                                        "foreground-set",
+		                                                        foreground_set,
+		                                                        NULL);
+
+		gtk_text_tag_set_priority (self->priv->highlight_tag,
+		                           gtk_text_tag_table_get_size (gtk_text_buffer_get_tag_table (buffer)) - 1);
+
+		g_signal_connect (gtk_text_buffer_get_tag_table (buffer),
+		                  "tag-added",
+		                  G_CALLBACK (on_tag_added),
+		                  self);
+	}
+
 	GtkTreeSelection *selection;
 
 	selection = gtk_tree_view_get_selection(self->priv->tree_view_unstaged);
@@ -1169,12 +1634,12 @@ gitg_commit_view_class_init(GitgCommitViewClass *klass)
 }
 
 static void
-gitg_commit_view_init(GitgCommitView *self)
+gitg_commit_view_init (GitgCommitView *self)
 {
-	self->priv = GITG_COMMIT_VIEW_GET_PRIVATE(self);
+	self->priv = GITG_COMMIT_VIEW_GET_PRIVATE (self);
 
-	self->priv->runner = gitg_runner_new(10000);
-	self->priv->hand = gdk_cursor_new(GDK_HAND1);
+	self->priv->runner = gitg_runner_new (10000);
+	self->priv->hand = gdk_cursor_new (GDK_HAND1);
 }
 
 void 
@@ -1800,31 +2265,36 @@ create_context_menu_item (GitgCommitView *view, gchar const *action)
 }
 
 static void 
-on_changes_view_popup_menu(GtkTextView *textview, GtkMenu *menu, GitgCommitView *view)
+on_changes_view_popup_menu (GtkTextView *textview, GtkMenu *menu, GitgCommitView *view)
 {
-	/* check the hunk */
-	if (!get_hunk_at_pointer(view, &view->priv->context_iter, NULL))
+	gboolean is_hunk;
+
+	get_info_at_pointer (view, &view->priv->context_iter, &is_hunk, NULL, NULL);
+
+	if (!is_hunk)
+	{
 		return;
+	}
 
-	GtkWidget *separator = gtk_separator_menu_item_new();
-	gtk_widget_show(separator);
+	GtkWidget *separator = gtk_separator_menu_item_new ();
+	gtk_widget_show (separator);
 
 	view->priv->context_type = CONTEXT_TYPE_HUNK;
 
-	gtk_menu_shell_append(GTK_MENU_SHELL(menu), separator);
+	gtk_menu_shell_append (GTK_MENU_SHELL (menu), separator);
 
 	if (view->priv->current_changes & GITG_CHANGED_FILE_CHANGES_CACHED)
 	{
-		GtkWidget *unstage = create_context_menu_item(view, "UnstageChangesAction");
-		gtk_menu_shell_append(GTK_MENU_SHELL(menu), unstage);
+		GtkWidget *unstage = create_context_menu_item (view, "UnstageChangesAction");
+		gtk_menu_shell_append (GTK_MENU_SHELL (menu), unstage);
 	}
 	else
 	{
-		GtkWidget *stage = create_context_menu_item(view, "StageChangesAction");
-		GtkWidget *revert = create_context_menu_item(view, "RevertChangesAction");
+		GtkWidget *stage = create_context_menu_item (view, "StageChangesAction");
+		GtkWidget *revert = create_context_menu_item (view, "RevertChangesAction");
 
-		gtk_menu_shell_append(GTK_MENU_SHELL(menu), stage);
-		gtk_menu_shell_append(GTK_MENU_SHELL(menu), revert);
+		gtk_menu_shell_append (GTK_MENU_SHELL (menu), stage);
+		gtk_menu_shell_append (GTK_MENU_SHELL (menu), revert);
 	}
 }
 
diff --git a/gitg/gitg-diff-line-renderer.c b/gitg/gitg-diff-line-renderer.c
index 567ef27..29561dc 100644
--- a/gitg/gitg-diff-line-renderer.c
+++ b/gitg/gitg-diff-line-renderer.c
@@ -21,6 +21,7 @@
  */
 
 #include "gitg-diff-line-renderer.h"
+#include "gitg-utils.h"
 
 #define GITG_DIFF_LINE_RENDERER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GITG_TYPE_DIFF_LINE_RENDERER, GitgDiffLineRendererPrivate))
 
@@ -37,13 +38,15 @@ enum
 {
 	PROP_0,
 	PROP_LINE_OLD,
-	PROP_LINE_NEW
+	PROP_LINE_NEW,
+	PROP_LABEL
 };
 
 struct _GitgDiffLineRendererPrivate
 {
 	gint line_old;
 	gint line_new;
+	gchar *label;
 };
 
 G_DEFINE_TYPE (GitgDiffLineRenderer, gitg_diff_line_renderer, GTK_TYPE_CELL_RENDERER)
@@ -70,6 +73,10 @@ gitg_diff_line_renderer_set_property (GObject      *object,
 		case PROP_LINE_NEW:
 			self->priv->line_new = g_value_get_int (value);
 		break;
+		case PROP_LABEL:
+			g_free (self->priv->label);
+			self->priv->label = g_value_dup_string (value);
+		break;
 		default:
 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -92,6 +99,9 @@ gitg_diff_line_renderer_get_property (GObject    *object,
 		case PROP_LINE_NEW:
 			g_value_set_int (value, self->priv->line_new);
 		break;
+		case PROP_LABEL:
+			g_value_set_string (value, self->priv->label);
+		break;
 		default:
 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -99,16 +109,109 @@ gitg_diff_line_renderer_get_property (GObject    *object,
 }
 
 static void
-gitg_diff_line_renderer_render_impl (GtkCellRenderer      *cell,
-                                     GdkDrawable          *window,
-                                     GtkWidget            *widget,
-                                     GdkRectangle         *background_area,
-                                     GdkRectangle         *cell_area,
-                                     GdkRectangle         *expose_area,
-                                     GtkCellRendererState  flags)
+darken_or_lighten (cairo_t        *ctx,
+                   GdkColor const *color)
 {
-	GitgDiffLineRenderer *lr = GITG_DIFF_LINE_RENDERER (cell);
+	float r, g, b;
+
+	r = color->red / 65535.0;
+	g = color->green / 65535.0;
+	b = color->blue / 65535.0;
+
+	if ((r + g + b) / 3 > 0.5)
+	{
+		cairo_set_source_rgb (ctx,
+		                      r * 0.5,
+		                      g * 0.5,
+		                      b * 0.5);
+	}
+	else
+	{
+		cairo_set_source_rgb (ctx,
+		                      r * 1.5,
+		                      g * 1.5,
+		                      b * 1.5);
+	}
+}
+
+static void
+render_label (GitgDiffLineRenderer *lr,
+              GdkDrawable          *window,
+              GtkWidget            *widget,
+              GdkRectangle         *background_area,
+              GdkRectangle         *cell_area,
+              GdkRectangle         *expose_area,
+              GtkCellRendererState  flags)
+{
+	PangoLayout *layout;
+	GtkStyle *style;
+	GtkStateType state;
+	gint pixel_height;
+
+	layout = gtk_widget_create_pango_layout (widget, "");
+
+	pango_layout_set_markup (layout, lr->priv->label, -1);
+	pango_layout_set_width (layout, cell_area->width);
+
+	pango_layout_get_pixel_size (layout, NULL, &pixel_height);
+
+	pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
+
+	style = gtk_widget_get_style (widget);
+	state = gtk_widget_get_state (widget);
+
+	cairo_t *ctx = gdk_cairo_create (window);
+
+	gdk_cairo_rectangle (ctx, expose_area);
+	cairo_clip (ctx);
+
+	gdk_cairo_set_source_color (ctx, &(style->fg[state]));
+
+	gitg_utils_rounded_rectangle (ctx,
+	                              cell_area->x + 0.5,
+	                              cell_area->y + 0.5,
+	                              cell_area->width - 1,
+	                              cell_area->height - 1,
+	                              5);
+
+	cairo_fill_preserve (ctx);
+
+	darken_or_lighten (ctx, &(style->fg[state]));
+
+	cairo_set_line_width (ctx, 1);
+	cairo_stroke (ctx);
+
+	gdk_cairo_set_source_color (ctx, &(style->base[state]));
+
+	cairo_move_to (ctx,
+	               cell_area->x + cell_area->width / 2,
+	               cell_area->y + (cell_area->height - pixel_height) / 2);
+
+	pango_cairo_show_layout (ctx, layout);
+
+	cairo_destroy (ctx);
 
+	/*gtk_paint_layout (style,
+	                  window,
+	                  state,
+	                  FALSE,
+	                  NULL,
+	                  widget,
+	                  NULL,
+	                  cell_area->x + cell_area->width / 2,
+	                  cell_area->y,
+	                  layout);*/
+}
+
+static void
+render_lines (GitgDiffLineRenderer *lr,
+              GdkDrawable          *window,
+              GtkWidget            *widget,
+              GdkRectangle         *background_area,
+              GdkRectangle         *cell_area,
+              GdkRectangle         *expose_area,
+              GtkCellRendererState  flags)
+{
 	/* Render new/old in the cell area */
 	gchar old_str[16];
 	gchar new_str[16];
@@ -138,7 +241,7 @@ gitg_diff_line_renderer_render_impl (GtkCellRenderer      *cell,
 		*new_str = '\0';
 	}
 
-	g_object_get (cell, "xpad", &xpad, "ypad", &ypad, NULL);
+	g_object_get (lr, "xpad", &xpad, "ypad", &ypad, NULL);
 
 	pango_layout_set_text (layout, old_str, -1);
 	gtk_paint_layout (widget->style,
@@ -178,6 +281,39 @@ gitg_diff_line_renderer_render_impl (GtkCellRenderer      *cell,
 }
 
 static void
+gitg_diff_line_renderer_render_impl (GtkCellRenderer      *cell,
+                                     GdkDrawable          *window,
+                                     GtkWidget            *widget,
+                                     GdkRectangle         *background_area,
+                                     GdkRectangle         *cell_area,
+                                     GdkRectangle         *expose_area,
+                                     GtkCellRendererState  flags)
+{
+	GitgDiffLineRenderer *lr = GITG_DIFF_LINE_RENDERER (cell);
+
+	if (lr->priv->label)
+	{
+		render_label (lr,
+		              window,
+		              widget,
+		              background_area,
+		              cell_area,
+		              expose_area,
+		              flags);
+	}
+	else
+	{
+		render_lines (lr,
+		              window,
+		              widget,
+		              background_area,
+		              cell_area,
+		              expose_area,
+		              flags);
+	}
+}
+
+static void
 gitg_diff_line_renderer_get_size_impl (GtkCellRenderer *cell,
                                        GtkWidget       *widget,
                                        GdkRectangle    *cell_area,
@@ -201,8 +337,35 @@ gitg_diff_line_renderer_get_size_impl (GtkCellRenderer *cell,
 	pango_layout_get_pixel_size(layout, &pixel_width, &pixel_height);
 
 	g_object_get (cell, "xpad", &xpad, "ypad", &ypad, NULL);
+	pixel_width += pixel_width + xpad * 2 + 3;
 
-	pixel_width = pixel_width * 2 + xpad * 4 + 3;
+	if (lr->priv->label)
+	{
+		PangoLayout *lbl_layout;
+		gint lbl_pixel_width;
+		gint lbl_pixel_height;
+
+		lbl_layout = gtk_widget_create_pango_layout (widget,
+		                                             "");
+
+		pango_layout_set_markup (lbl_layout, lr->priv->label, -1);
+
+		pango_layout_get_pixel_size (lbl_layout,
+		                             &lbl_pixel_width,
+		                             &lbl_pixel_height);
+
+		if (lbl_pixel_width > pixel_width)
+		{
+			pixel_width = lbl_pixel_width;
+		}
+
+		if (lbl_pixel_height > pixel_height)
+		{
+			pixel_height = lbl_pixel_height;
+		}
+	}
+
+	pixel_width += xpad * 2;
 	pixel_height += ypad * 2;
 
 	if (width)
@@ -261,7 +424,15 @@ gitg_diff_line_renderer_class_init (GitgDiffLineRendererClass *klass)
 	                                                   -1,
 	                                                   G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
 
-	g_type_class_add_private (object_class, sizeof(GitgDiffLineRendererPrivate));
+	g_object_class_install_property (object_class,
+	                                 PROP_LABEL,
+	                                 g_param_spec_string ("label",
+	                                                      "Label",
+	                                                      "Label",
+	                                                      NULL,
+	                                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+	g_type_class_add_private (object_class, sizeof (GitgDiffLineRendererPrivate));
 }
 
 static void
diff --git a/gitg/gitg-diff-view.c b/gitg/gitg-diff-view.c
index ed363a5..db97dc3 100644
--- a/gitg/gitg-diff-view.c
+++ b/gitg/gitg-diff-view.c
@@ -23,6 +23,7 @@
 #include "gitg-diff-view.h"
 #include "gitg-types.h"
 #include "gitg-diff-line-renderer.h"
+#include "gitg-utils.h"
 
 #include <string.h>
 #include <stdlib.h>
@@ -39,11 +40,11 @@
 
 #define IDLE_SCAN_COUNT 30
 
-#define GITG_DIFF_ITER_GET_VIEW(iter) ((GitgDiffView *)iter->userdata)
-#define GITG_DIFF_ITER_GET_REGION(iter) ((Region *)iter->userdata2)
+#define GITG_DIFF_ITER_GET_VIEW(iter) ((GitgDiffView *)((iter)->userdata))
+#define GITG_DIFF_ITER_GET_REGION(iter) ((Region *)((iter)->userdata2))
 
-#define GITG_DIFF_ITER_SET_REGION(iter, region) (iter->userdata2 = region)
-#define GITG_DIFF_ITER_SET_VIEW(iter, view) (iter->userdata = view)
+#define GITG_DIFF_ITER_SET_REGION(iter, region) ((iter)->userdata2 = region)
+#define GITG_DIFF_ITER_SET_VIEW(iter, view) ((iter)->userdata = view)
 
 static void on_buffer_insert_text(GtkTextBuffer *buffer, GtkTextIter *iter, gchar const *text, gint len, GitgDiffView *view);
 static void on_buffer_delete_range(GtkTextBuffer *buffer, GtkTextIter *start, GtkTextIter *end, GitgDiffView *view);
@@ -124,6 +125,12 @@ struct _GitgDiffViewPrivate
 	Region *lines_current_region;
 	gint lines_previous_line;
 	guint lines_counters[2];
+
+	gboolean ignore_changes;
+
+	GitgDiffViewLabelFunc label_func;
+	gpointer label_func_user_data;
+	GDestroyNotify label_func_destroy_notify;
 };
 
 G_DEFINE_TYPE(GitgDiffView, gitg_diff_view, GTK_TYPE_SOURCE_VIEW)
@@ -186,6 +193,12 @@ gitg_diff_view_finalize (GObject *object)
 	regions_free (view, TRUE);
 	g_sequence_free (view->priv->regions_index);
 
+	if (view->priv->label_func &&
+	    view->priv->label_func_destroy_notify)
+	{
+		view->priv->label_func_destroy_notify (view->priv->label_func_user_data);
+	}
+
 	G_OBJECT_CLASS (gitg_diff_view_parent_class)->finalize (object);
 }
 
@@ -395,7 +408,7 @@ region_to_iter (GitgDiffView *view, Region *region, GitgDiffIter *iter)
 }
 
 static void
-add_region(GitgDiffView *view, Region *region)
+add_region (GitgDiffView *view, Region *region)
 {
 	if (view->priv->last_region)
 	{
@@ -404,7 +417,7 @@ add_region(GitgDiffView *view, Region *region)
 
 		if (view->priv->last_region->type == GITG_DIFF_ITER_TYPE_HUNK)
 		{
-			ensure_max_line(view, (Hunk *)view->priv->last_region);
+			ensure_max_line (view, (Hunk *)view->priv->last_region);
 		}
 	}
 	else
@@ -622,6 +635,16 @@ line_renderer_size_func (GtkSourceGutter *gutter,
 	              "line_old", view->priv->max_line_count,
 	              "line_new", view->priv->max_line_count,
 	              NULL);
+
+	if (view->priv->label_func)
+	{
+		gchar *label = view->priv->label_func (view,
+		                                       -1,
+		                                       view->priv->label_func_user_data);
+
+		g_object_set (cell, "label", label, NULL);
+		g_free (label);
+	}
 }
 
 static void
@@ -676,6 +699,16 @@ line_renderer_data_func (GtkSourceGutter *gutter,
 		view->priv->lines_counters[0] = view->priv->lines_counters[1] = 0;
 		*current = (*current)->next->visible ? (*current)->next : NULL;
 	}
+
+	if (view->priv->label_func)
+	{
+		gchar *label = view->priv->label_func (view,
+		                                       line_number,
+		                                       view->priv->label_func_user_data);
+
+		g_object_set (cell, "label", label, NULL);
+		g_free (label);
+	}
 }
 
 static gint
@@ -710,44 +743,176 @@ gitg_diff_view_set_diff_enabled(GitgDiffView *view, gboolean enabled)
 	g_object_notify(G_OBJECT(view), "diff-enabled");
 }
 
-void
-gitg_diff_view_remove_hunk(GitgDiffView *view, GtkTextIter *iter)
+static void
+offset_regions (Region *region,
+                gint    offset)
 {
-	g_return_if_fail(GITG_IS_DIFF_VIEW(view));
+	while (region)
+	{
+		region->line += offset;
+		region = region->next;
+	}
+}
 
-	/* removes hunk at iter and if it was the last hunk of a file, also removes
-	   the file header */
-	Region *region = find_current_region(view, gtk_text_iter_get_line(iter));
+static gint
+compare_regions (Region   *first,
+                 Region   *second,
+                 gpointer  user_data)
+{
+	return first->line < second->line ? -1 : (first->line > second->line ? 1 : 0);
+}
 
-	if (!region)
-		return;
+static GSequenceIter *
+region_get_iter (GitgDiffView *view, Region *region)
+{
+	GSequenceIter *iter;
 
+	iter = g_sequence_search (view->priv->regions_index,
+	                          region,
+	                          (GCompareDataFunc)compare_regions,
+	                          NULL);
+
+	if (g_sequence_iter_is_end (iter))
+	{
+		return g_sequence_iter_prev (iter);
+	}
+	else
+	{
+		Region *reg = g_sequence_get (iter);
+
+		if (reg->line != region->line)
+		{
+			return g_sequence_iter_prev (iter);
+		}
+		else
+		{
+			return iter;
+		}
+	}
+}
+
+static void
+remove_regions_sequence (GitgDiffView *view,
+                         Region       *from,
+                         Region       *to)
+{
+	GSequenceIter *start;
+	GSequenceIter *end;
+
+	start = region_get_iter (view, from);
+
+	if (to)
+	{
+		end = g_sequence_iter_prev (region_get_iter (view, to));
+	}
+	else
+	{
+		end = g_sequence_get_end_iter (view->priv->regions_index);
+	}
+
+	g_sequence_remove_range (start, end);
+}
+
+static void
+remove_regions (GitgDiffView *view, Region *from, Region *to)
+{
+	GtkTextBuffer *buffer;
 	GtkTextIter start;
 	GtkTextIter end;
+	gint offset;
 
-	gtk_text_buffer_get_iter_at_line(view->priv->current_buffer, &start, region->line);
+	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
 
-	if (region->next)
+	if (from->prev)
 	{
-		gtk_text_buffer_get_iter_at_line(view->priv->current_buffer, &end, region->next->line - 1);
-		gtk_text_iter_forward_line(&end);
+		if (to)
+		{
+			from->prev->next = to;
+			to->prev = from->prev;
+		}
+		else
+		{
+			from->prev->next = NULL;
+		}
 	}
 	else
 	{
-		gtk_text_buffer_get_end_iter(view->priv->current_buffer, &end);
+		if (to)
+		{
+			view->priv->regions = to;
+			to->prev = NULL;
+		}
+		else
+		{
+			view->priv->regions = NULL;
+		}
+	}
+
+	if (!to)
+	{
+		view->priv->last_region = from->prev;
 	}
 
-	Region *prev = find_current_region(view, region->line - 1);
+	remove_regions_sequence (view, from, to);
+
+	gtk_text_buffer_get_iter_at_line (buffer, &start, from->line);
 
-	if ((!region->next || region->next->type == GITG_DIFF_ITER_TYPE_HEADER) && (!prev || prev->type == GITG_DIFF_ITER_TYPE_HEADER))
+	if (to)
 	{
-		if (!prev)
-			gtk_text_buffer_get_start_iter(view->priv->current_buffer, &start);
-		else
-			gtk_text_buffer_get_iter_at_line(view->priv->current_buffer, &start, region->line);
+		gtk_text_buffer_get_iter_at_line (buffer, &end, to->line);
+	}
+	else
+	{
+		gtk_text_buffer_get_end_iter (buffer, &end);
+	}
+
+	/* Remove and free from sequence */
+	while (from && from != to)
+	{
+		Region *next = from->next;
+
+		//region_free (from);
+		from = next;
 	}
 
-	gtk_text_buffer_delete(view->priv->current_buffer, &start, &end);
+	offset = gtk_text_iter_get_line (&start) - gtk_text_iter_get_line (&end);
+
+	offset_regions (to, offset);
+
+	view->priv->ignore_changes = TRUE;
+	gtk_text_buffer_begin_user_action (buffer);
+	gtk_text_buffer_delete (buffer, &start, &end);
+	gtk_text_buffer_end_user_action (buffer);
+	view->priv->ignore_changes = FALSE;
+}
+
+void
+gitg_diff_view_remove_hunk (GitgDiffView *view, GtkTextIter *iter)
+{
+	g_return_if_fail (GITG_IS_DIFF_VIEW (view));
+	g_return_if_fail (iter != NULL);
+
+	/* removes hunk at iter and if it was the last hunk of a file, also removes
+	   the file header */
+	Region *region = find_current_region (view,
+	                                      gtk_text_iter_get_line (iter));
+
+	if (!region)
+	{
+		return;
+	}
+
+	Region *from = region;
+	Region *to = region->next;
+
+	if (region->prev && region->prev->type == GITG_DIFF_ITER_TYPE_HEADER &&
+	    (!to || to->type == GITG_DIFF_ITER_TYPE_HEADER))
+	{
+		/* also remove the header in this case */
+		from = region->prev;
+	}
+
+	remove_regions (view, from, to);
 }
 
 gboolean
@@ -1014,6 +1179,11 @@ try_scan(GitgDiffView *view)
 static void
 on_buffer_delete_range(GtkTextBuffer *buffer, GtkTextIter *start, GtkTextIter *end, GitgDiffView *view)
 {
+	if (view->priv->ignore_changes)
+	{
+		return;
+	}
+
 	regions_free(view, FALSE);
 
 	if (iter_in_view(view, start) || iter_in_view(view, end))
@@ -1026,6 +1196,11 @@ on_buffer_delete_range(GtkTextBuffer *buffer, GtkTextIter *start, GtkTextIter *e
 static void 
 on_buffer_insert_text(GtkTextBuffer *buffer, GtkTextIter *iter, gchar const *text, gint len, GitgDiffView *view)
 {
+	if (view->priv->ignore_changes)
+	{
+		return;
+	}
+
 	/* if region is in current view and not scanned, issue scan now */
 	if (iter_in_view(view, iter))
 		try_scan(view);
@@ -1088,6 +1263,29 @@ gitg_diff_view_get_hunk_at_iter (GitgDiffView *view,
 	return TRUE;
 }
 
+static void
+region_get_bounds (GitgDiffView *view,
+                   Region *region,
+                   GtkTextIter *start,
+                   GtkTextIter *end)
+{
+	gtk_text_buffer_get_iter_at_line (view->priv->current_buffer,
+	                                  start,
+	                                  region->line);
+
+	if (region->next != NULL)
+	{
+		gtk_text_buffer_get_iter_at_line (view->priv->current_buffer,
+		                                  end,
+		                                  region->next->line);
+	}
+	else
+	{
+		gtk_text_buffer_get_end_iter (view->priv->current_buffer,
+		                              end);
+	}
+}
+
 void
 gitg_diff_iter_get_bounds (GitgDiffIter const *iter,
                            GtkTextIter *start,
@@ -1102,20 +1300,190 @@ gitg_diff_iter_get_bounds (GitgDiffIter const *iter,
 	GitgDiffView *view = GITG_DIFF_ITER_GET_VIEW (iter);
 	Region *region = GITG_DIFF_ITER_GET_REGION (iter);
 
-	gtk_text_buffer_get_iter_at_line (view->priv->current_buffer,
-	                                  start,
-	                                  region->line);
+	region_get_bounds (view, region, start, end);
+}
 
-	if (region->next != NULL)
+GitgDiffLineType
+gitg_diff_view_get_line_type (GitgDiffView *view, GtkTextIter const *iter)
+{
+	g_return_val_if_fail (GITG_IS_DIFF_VIEW (view), GITG_DIFF_LINE_TYPE_NONE);
+	g_return_val_if_fail (iter != NULL, GITG_DIFF_LINE_TYPE_NONE);
+
+	GitgDiffIter diff_iter;
+
+	if (!gitg_diff_view_get_hunk_at_iter (view, iter, &diff_iter))
 	{
-		gtk_text_buffer_get_iter_at_line (view->priv->current_buffer,
-		                                  end,
-		                                  region->next->line);
+		return GITG_DIFF_LINE_TYPE_NONE;
+	}
+
+	GtkTextIter start = *iter;
+	gtk_text_iter_set_line_offset (&start, 0);
+
+	gunichar ch = gtk_text_iter_get_char (&start);
+
+	switch (ch)
+	{
+		case '+':
+			return GITG_DIFF_LINE_TYPE_ADD;
+		case '-':
+			return GITG_DIFF_LINE_TYPE_REMOVE;
+		default:
+			return GITG_DIFF_LINE_TYPE_NONE;
+	}
+}
+
+static void
+calculate_hunk_header_counters (GitgDiffView *view,
+                                Region       *region)
+{
+	GtkTextIter start;
+	GtkTextIter end;
+	GtkTextIter begin;
+
+	GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+
+	region_get_bounds (view, region, &start, &end);
+
+	begin = start;
+
+	guint new_count = 0;
+	guint old_count = 0;
+
+	gboolean isempty = TRUE;
+
+	if (gtk_text_iter_forward_line (&start))
+	{
+		while (gtk_text_iter_compare (&start, &end) < 0)
+		{
+			GitgDiffLineType line_type;
+			GtkTextIter line_end = start;
+
+			gtk_text_iter_forward_to_line_end (&line_end);
+
+			line_type = gitg_diff_view_get_line_type (view, &start);
+
+			if (line_type == GITG_DIFF_LINE_TYPE_NONE ||
+			    line_type == GITG_DIFF_LINE_TYPE_ADD)
+			{
+				++new_count;
+			}
+
+			if (line_type == GITG_DIFF_LINE_TYPE_NONE ||
+			    line_type == GITG_DIFF_LINE_TYPE_REMOVE)
+			{
+				++old_count;
+			}
+
+			if (line_type != GITG_DIFF_LINE_TYPE_NONE)
+			{
+				isempty = FALSE;
+			}
+
+			if (!gtk_text_iter_forward_line (&start))
+			{
+				break;
+			}
+		}
+	}
+
+	if (isempty)
+	{
+		gitg_diff_view_remove_hunk (view, &begin);
 	}
 	else
 	{
-		gtk_text_buffer_get_end_iter (view->priv->current_buffer,
-		                              end);
+		end = begin;
+		gtk_text_iter_forward_to_line_end (&end);
+
+		gchar *header = gtk_text_buffer_get_text (buffer, &begin, &end, TRUE);
+		gchar *ret;
+
+		ret = gitg_utils_rewrite_hunk_counters (header, old_count, new_count);
+		g_free (header);
+
+		gtk_text_buffer_delete (buffer, &begin, &end);
+		gtk_text_buffer_insert (buffer, &begin, ret, -1);
+
+		g_free (ret);
+	}
+}
+
+void
+gitg_diff_view_clear_line (GitgDiffView *view,
+                           GtkTextIter const *iter,
+                           GitgDiffLineType old_type,
+                           GitgDiffLineType new_type)
+{
+	g_return_if_fail (GITG_IS_DIFF_VIEW (view));
+	g_return_if_fail (iter != NULL);
+
+	GitgDiffLineType line_type;
+	GitgDiffIter diff_iter;
+
+	line_type = gitg_diff_view_get_line_type (view, iter);
+
+	if (line_type == GITG_DIFF_LINE_TYPE_NONE)
+	{
+		return;
+	}
+
+	gitg_diff_view_get_hunk_at_iter (view, iter, &diff_iter);
+
+	GtkTextIter start = *iter;
+	GtkTextIter end;
+	GtkTextBuffer *buffer;
+	Region *region;
+
+	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+
+	gtk_text_iter_set_line_offset (&start, 0);
+	end = start;
+
+	gtk_text_buffer_begin_user_action (buffer);
+	view->priv->ignore_changes = TRUE;
+
+	region = GITG_DIFF_ITER_GET_REGION (&diff_iter);
+
+	if (line_type == new_type)
+	{
+		/* means the line now just becomes context */
+		gtk_text_iter_forward_char (&end);
+		gtk_text_buffer_delete (buffer, &start, &end);
+		gtk_text_buffer_insert (buffer, &start, " ", 1);
 	}
+	else
+	{
+		/* means the line should be removed */
+		if (!gtk_text_iter_forward_line (&end))
+		{
+			gtk_text_iter_forward_to_line_end (&end);
+		}
+
+		gtk_text_buffer_delete (buffer, &start, &end);
+		offset_regions (region->next, -1);
+	}
+
+	calculate_hunk_header_counters (view, region);
+
+	view->priv->ignore_changes = FALSE;
+	gtk_text_buffer_end_user_action (buffer);
 }
 
+void
+gitg_diff_view_set_label_func (GitgDiffView *view,
+                               GitgDiffViewLabelFunc func,
+                               gpointer user_data,
+                               GDestroyNotify destroy_notify)
+{
+	g_return_if_fail (GITG_IS_DIFF_VIEW (view));
+
+	if (view->priv->label_func &&
+	    view->priv->label_func_destroy_notify)
+	{
+		view->priv->label_func_destroy_notify (view->priv->label_func_user_data);
+	}
+
+	view->priv->label_func = func;
+	view->priv->label_func_user_data = user_data;
+	view->priv->label_func_destroy_notify = destroy_notify;
+}
diff --git a/gitg/gitg-diff-view.h b/gitg/gitg-diff-view.h
index d7b7946..4aa294e 100644
--- a/gitg/gitg-diff-view.h
+++ b/gitg/gitg-diff-view.h
@@ -54,6 +54,13 @@ typedef enum
 	GITG_DIFF_ITER_TYPE_HUNK
 } GitgDiffIterType;
 
+typedef enum
+{
+	GITG_DIFF_LINE_TYPE_NONE,
+	GITG_DIFF_LINE_TYPE_ADD,
+	GITG_DIFF_LINE_TYPE_REMOVE,
+} GitgDiffLineType;
+
 struct _GitgDiffView
 {
 	GtkSourceView parent;
@@ -69,6 +76,10 @@ struct _GitgDiffViewClass
 	void (*hunk_added)(GitgDiffView *view, GitgDiffIter *iter);
 };
 
+typedef gchar *(*GitgDiffViewLabelFunc) (GitgDiffView *view,
+                                         gint          line,
+                                         gpointer      user_data);
+
 GType gitg_diff_view_get_type(void) G_GNUC_CONST;
 GitgDiffView *gitg_diff_view_new(void);
 
@@ -91,6 +102,14 @@ gboolean gitg_diff_view_get_hunk_at_iter (GitgDiffView *view, GtkTextIter const
 
 void gitg_diff_iter_get_bounds (GitgDiffIter const *iter, GtkTextIter *start, GtkTextIter *end);
 
+GitgDiffLineType gitg_diff_view_get_line_type (GitgDiffView *view, GtkTextIter const *iter);
+void gitg_diff_view_clear_line (GitgDiffView *view, GtkTextIter const *iter, GitgDiffLineType old_type, GitgDiffLineType new_type);
+
+void gitg_diff_view_set_label_func (GitgDiffView *view,
+                                    GitgDiffViewLabelFunc func,
+                                    gpointer user_data,
+                                    GDestroyNotify destroy_notify);
+
 G_END_DECLS
 
 #endif /* __GITG_DIFF_VIEW_H__ */
diff --git a/gitg/gitg-label-renderer.c b/gitg/gitg-label-renderer.c
index 442304f..bd5f3b2 100644
--- a/gitg/gitg-label-renderer.c
+++ b/gitg/gitg-label-renderer.c
@@ -22,6 +22,8 @@
 
 #include "gitg-label-renderer.h"
 #include "gitg-ref.h"
+#include "gitg-utils.h"
+
 #include <math.h>
 
 #define PADDING 4
@@ -67,23 +69,6 @@ gitg_label_renderer_width(GtkWidget *widget, PangoFontDescription *description,
 }
 
 static void
-rounded_rectangle(cairo_t *ctx, float x, float y, float width, float height, float radius)
-{
-	cairo_move_to(ctx, x + radius, y);
-	cairo_rel_line_to(ctx, width - 2 * radius, 0);
-	cairo_arc(ctx, x + width - radius, y + radius, radius, 1.5 * M_PI, 0.0);
-
-	cairo_rel_line_to(ctx, 0, height - 2 * radius);
-	cairo_arc(ctx, x + width - radius, y + height - radius, radius, 0.0, 0.5 * M_PI);
-
-	cairo_rel_line_to(ctx, -(width - radius * 2), 0);
-	cairo_arc(ctx, x + radius, y + height - radius, radius, 0.5 * M_PI, M_PI);
-
-	cairo_rel_line_to(ctx, 0, -(height - radius * 2));
-	cairo_arc(ctx, x + radius, y + radius, radius, M_PI, 1.5 * M_PI);
-}
-
-static void
 get_type_color (GitgRefType type, gdouble *r, gdouble *g, gdouble *b)
 {
 	switch (type)
@@ -177,7 +162,11 @@ render_label (cairo_t *context, PangoLayout *layout, GitgRef *ref, gint x, gint
 	pango_layout_get_pixel_size(layout, &w, &h);
 
 	// draw rounded rectangle
-	rounded_rectangle(context, x + 0.5, y + MARGIN + 0.5, w + PADDING * 2, height - MARGIN * 2, 5);
+	gitg_utils_rounded_rectangle (context, x + 0.5,
+	                              y + MARGIN + 0.5,
+	                              w + PADDING * 2,
+	                              height - MARGIN * 2,
+	                              5);
 
 	set_source_for_ref_type(context, ref, use_state);
 	cairo_fill_preserve(context);
diff --git a/gitg/gitg-utils.c b/gitg/gitg-utils.c
index c66c908..705d19b 100644
--- a/gitg/gitg-utils.c
+++ b/gitg/gitg-utils.c
@@ -24,6 +24,7 @@
 #include <glib.h>
 #include <stdlib.h>
 #include <gconf/gconf-client.h>
+#include <math.h>
 
 #include "gitg-utils.h"
 #include "gitg-dirs.h"
@@ -596,3 +597,78 @@ gitg_utils_restore_pane_position (GtkPaned *paned, gint position, gboolean rever
 	                       (GClosureNotify)free_paned_restore_info,
 	                       G_CONNECT_AFTER);
 }
+
+gchar *
+gitg_utils_rewrite_hunk_counters (gchar const *header,
+                                  guint old_count,
+                                  guint new_count)
+{
+	if (!header)
+	{
+		return NULL;
+	}
+
+	gchar *copy = g_strdup (header);
+	gchar *ptr1 = g_utf8_strchr (copy, -1, ',');
+
+	if (!ptr1)
+	{
+		g_free (copy);
+		return NULL;
+	}
+
+	gchar *ptrs1 = g_utf8_strchr (ptr1 + 1, -1, ' ');
+
+	if (!ptrs1)
+	{
+		g_free (copy);
+		return NULL;
+	}
+
+	gchar *ptr2 = g_utf8_strchr (ptrs1 + 1, -1, ',');
+
+	if (!ptr2)
+	{
+		g_free (copy);
+		return NULL;
+	}
+
+	gchar *ptrs2 = g_utf8_strchr (ptr2 + 1, -1, ' ');
+
+	if (!ptrs2)
+	{
+		g_free (copy);
+		return NULL;
+	}
+
+	*ptr1 = *ptr2 = '\0';
+
+	gchar *ret;
+
+	ret = g_strdup_printf ("%s,%d%s,%d%s",
+	                       copy,
+	                       old_count,
+	                       ptrs1,
+	                       new_count,
+	                       ptrs2);
+
+	g_free (copy);
+	return ret;
+}
+
+void
+gitg_utils_rounded_rectangle(cairo_t *ctx, gdouble x, gdouble y, gdouble width, gdouble height, gdouble radius)
+{
+	cairo_move_to (ctx, x + radius, y);
+	cairo_rel_line_to (ctx, width - 2 * radius, 0);
+	cairo_arc (ctx, x + width - radius, y + radius, radius, 1.5 * M_PI, 0.0);
+
+	cairo_rel_line_to (ctx, 0, height - 2 * radius);
+	cairo_arc (ctx, x + width - radius, y + height - radius, radius, 0.0, 0.5 * M_PI);
+
+	cairo_rel_line_to (ctx, -(width - radius * 2), 0);
+	cairo_arc (ctx, x + radius, y + height - radius, radius, 0.5 * M_PI, M_PI);
+
+	cairo_rel_line_to (ctx, 0, -(height - radius * 2));
+	cairo_arc (ctx, x + radius, y + radius, radius, M_PI, 1.5 * M_PI);
+}
diff --git a/gitg/gitg-utils.h b/gitg/gitg-utils.h
index 1ae8d3c..fe3008b 100644
--- a/gitg/gitg-utils.h
+++ b/gitg/gitg-utils.h
@@ -71,4 +71,8 @@ GtkCellRenderer *gitg_utils_find_cell_at_pos (GtkTreeView *tree_view, GtkTreeVie
 
 void gitg_utils_restore_pane_position (GtkPaned *paned, gint position, gboolean reversed);
 
+gchar *gitg_utils_rewrite_hunk_counters (gchar const *hunk, guint old_count, guint new_count);
+void gitg_utils_rounded_rectangle (cairo_t *ctx, gdouble x, gdouble y, gdouble width, gdouble height, gdouble radius);
+
+
 #endif /* __GITG_UTILS_H__ */



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