diff --git a/gitg/commit/gitg-commit.vala b/gitg/commit/gitg-commit.vala index 1e95473d..dd7ca929 100644 --- a/gitg/commit/gitg-commit.vala +++ b/gitg/commit/gitg-commit.vala @@ -1807,6 +1807,7 @@ namespace GitgCommit } }); + d_main.diff_view.repository = application.repository; d_main.diff_view.default_collapse_all = false; d_main.sidebar.deselected.connect(() => { diff --git a/libgitg/gitg-diff-view-file.vala b/libgitg/gitg-diff-view-file.vala index ccf360d2..4185fab4 100644 --- a/libgitg/gitg-diff-view-file.vala +++ b/libgitg/gitg-diff-view-file.vala @@ -20,6 +20,21 @@ [GtkTemplate (ui = "/org/gnome/gitg/ui/gitg-diff-view-file.ui")] class Gitg.DiffViewFile : Gtk.Grid { + private enum RegionType + { + ADDED, + REMOVED, + CONTEXT + } + + private struct Region + { + public RegionType type; + public int buffer_line_start; + public int source_line_start; + public int length; + } + [GtkChild( name = "expander" )] private Gtk.Expander d_expander; @@ -39,10 +54,20 @@ class Gitg.DiffViewFile : Gtk.Grid private uint d_removed; private bool d_expanded; private int64 d_doffset; - private Gee.HashMap d_lines; - private DiffViewFileSelectable d_selectable; + private DiffViewLinesRenderer d_old_lines; + private DiffViewLinesRenderer d_new_lines; + private DiffViewLinesRenderer d_sym_lines; + private bool d_highlight; + private Repository? d_repository; + private Cancellable? d_higlight_cancellable; + private Gtk.SourceBuffer? d_old_highlight_buffer; + private Gtk.SourceBuffer? d_new_highlight_buffer; + private bool d_old_highlight_ready; + private bool d_new_highlight_ready; + private Ggit.DiffDelta? d_delta; + private Region[] d_regions; public bool expanded { @@ -100,32 +125,47 @@ class Gitg.DiffViewFile : Gtk.Grid } } - public int maxlines + public int maxlines { get; set; } + public bool has_selection { get; private set; } + public bool handle_selection { get; construct set; } + + public Ggit.DiffDelta? delta { - get; set; + get { return d_delta; } + construct set + { + d_delta = value; + update_highlight(); + } } - public bool has_selection + public bool highlight { - get; - private set; + get { return d_highlight; } + + construct set + { + d_highlight = value; + update_highlight(); + } + + default = true; + } + + public Repository repository + { + get { return d_repository; } + + construct set + { + d_repository = value; + update_highlight(); + } } - public Ggit.DiffDelta delta + public DiffViewFile(Repository repository, Ggit.DiffDelta delta, bool handle_selection) { - get; - construct set; - } - - public bool handle_selection - { - get; - construct set; - } - - public DiffViewFile(Ggit.DiffDelta delta, bool handle_selection) - { - Object(delta: delta, handle_selection: handle_selection); + Object(repository: repository, delta: delta, handle_selection: handle_selection); } public PatchSet selection @@ -175,10 +215,6 @@ class Gitg.DiffViewFile : Gtk.Grid } } - private DiffViewLinesRenderer d_old_lines; - private DiffViewLinesRenderer d_new_lines; - private DiffViewLinesRenderer d_sym_lines; - construct { var gutter = d_sourceview_hunks.get_gutter(Gtk.TextWindowType.LEFT); @@ -221,6 +257,204 @@ class Gitg.DiffViewFile : Gtk.Grid d_sourceview_hunks.draw.connect_after(sourceview_hunks_on_draw); } + protected override void dispose() + { + base.dispose(); + + if (d_higlight_cancellable != null) + { + d_higlight_cancellable.cancel(); + d_higlight_cancellable = null; + } + } + + private void update_highlight() + { + if (d_higlight_cancellable != null) + { + d_higlight_cancellable.cancel(); + d_higlight_cancellable = null; + } + + d_old_highlight_buffer = null; + d_new_highlight_buffer = null; + + d_old_highlight_ready = false; + d_new_highlight_ready = false; + + if (highlight && repository != null && delta != null) + { + var cancellable = new Cancellable(); + d_higlight_cancellable = cancellable; + + init_highlighting_buffer.begin(delta.get_old_file(), cancellable, (obj, res) => { + var buffer = init_highlighting_buffer.end(res); + + if (!cancellable.is_cancelled()) + { + d_old_highlight_buffer = buffer; + d_old_highlight_ready = true; + + update_highlighting_ready(); + } + }); + + init_highlighting_buffer.begin(delta.get_new_file(), cancellable, (obj, res) => { + var buffer = init_highlighting_buffer.end(res); + + if (!cancellable.is_cancelled()) + { + d_new_highlight_buffer = buffer; + d_new_highlight_ready = true; + + update_highlighting_ready(); + } + }); + } + else + { + update_highlighting_ready(); + } + } + + private async Gtk.SourceBuffer? init_highlighting_buffer(Ggit.DiffFile file, Cancellable cancellable) + { + var id = file.get_oid(); + + if (id.is_zero()) + { + return null; + } + + var sfile = new Gtk.SourceFile(); + sfile.location = repository.get_workdir().get_child(file.get_path()); + + var basename = sfile.location.get_basename(); + + Ggit.Blob blob; + + try + { + blob = repository.lookup(id); + } + catch + { + return null; + } + + var content = blob.get_raw_content(); + + bool uncertain; + var content_type = GLib.ContentType.guess(basename, content, out uncertain); + + var bytes = new Bytes(content); + var stream = new GLib.MemoryInputStream.from_bytes(bytes); + + var manager = Gtk.SourceLanguageManager.get_default(); + var language = manager.guess_language(basename, content_type); + + if (language == null) + { + return null; + } + + var buffer = new Gtk.SourceBuffer(d_sourceview_hunks.buffer.tag_table); + + var style_scheme_manager = Gtk.SourceStyleSchemeManager.get_default(); + + buffer.language = language; + buffer.highlight_syntax = true; + buffer.style_scheme = style_scheme_manager.get_scheme("classic"); + + var loader = new Gtk.SourceFileLoader.from_stream(buffer, sfile, stream); + + try + { + yield loader.load_async(GLib.Priority.LOW, cancellable, null); + } + catch (Error e) + { + stderr.printf(@"ERROR: failed to load $(file.get_path()) for highlighting: $(e.message)\n"); + return null; + } + + return buffer; + } + + private void update_highlighting_ready() + { + if (!d_old_highlight_ready && !d_new_highlight_ready) + { + // Remove highlights + return; + } + else if (!d_old_highlight_ready || !d_new_highlight_ready) + { + // Both need to be loaded + return; + } + + var buffer = d_sourceview_hunks.buffer; + + // Go over all the source chunks and match up to old/new buffer. Then, + // apply the tags that are applied to the highlighted source buffers. + foreach (var region in d_regions) + { + Gtk.SourceBuffer? source; + + if (region.type == RegionType.REMOVED) + { + source = d_old_highlight_buffer; + } + else + { + source = d_new_highlight_buffer; + } + + if (source == null) + { + continue; + } + + Gtk.TextIter buffer_iter, source_iter; + + buffer.get_iter_at_line(out buffer_iter, region.buffer_line_start); + source.get_iter_at_line(out source_iter, region.source_line_start); + + var source_end_iter = source_iter; + source_end_iter.forward_lines(region.length); + + source.ensure_highlight(source_iter, source_end_iter); + + var buffer_end_iter = buffer_iter; + buffer_end_iter.forward_lines(region.length); + + var source_next_iter = source_iter; + var tags = source_iter.get_tags(); + + while (source_next_iter.forward_to_tag_toggle(null) && source_next_iter.compare(source_end_iter) < 0) + { + var buffer_next_iter = buffer_iter; + buffer_next_iter.forward_chars(source_next_iter.get_offset() - source_iter.get_offset()); + + foreach (var tag in tags) + { + buffer.apply_tag(tag, buffer_iter, buffer_next_iter); + } + + source_iter = source_next_iter; + buffer_iter = buffer_next_iter; + + tags = source_iter.get_tags(); + } + + foreach (var tag in tags) + { + buffer.apply_tag(tag, buffer_iter, buffer_end_iter); + } + } + } + private bool sourceview_hunks_on_draw(Cairo.Context cr) { var win = d_sourceview_hunks.get_window(Gtk.TextWindowType.LEFT); @@ -338,6 +572,13 @@ class Gitg.DiffViewFile : Gtk.Grid /* Diff Content */ var content = new StringBuilder(); + var region = Region() { + type = RegionType.CONTEXT, + buffer_line_start = 0, + source_line_start = 0, + length = 0 + }; + for (var i = 0; i < lines.size; i++) { var line = lines[i]; @@ -346,15 +587,21 @@ class Gitg.DiffViewFile : Gtk.Grid var removed = false; var origin = line.get_origin(); + var rtype = RegionType.CONTEXT; + switch (origin) { case Ggit.DiffLineType.ADDITION: added = true; d_added++; + + rtype = RegionType.ADDED; break; case Ggit.DiffLineType.DELETION: removed = true; d_removed++; + + rtype = RegionType.REMOVED; break; case Ggit.DiffLineType.CONTEXT_EOFNL: case Ggit.DiffLineType.ADD_EOFNL: @@ -363,6 +610,34 @@ class Gitg.DiffViewFile : Gtk.Grid break; } + if (i == 0 || rtype != region.type) + { + if (i != 0) + { + d_regions += region; + } + + int source_line_start; + + if (rtype == RegionType.REMOVED) + { + source_line_start = line.get_old_lineno() - 1; + } + else + { + source_line_start = line.get_new_lineno() - 1; + } + + region = Region() { + type = rtype, + buffer_line_start = buffer_line, + source_line_start = source_line_start, + length = 0 + }; + } + + region.length++; + if (added || removed) { var offset = (size_t)line.get_content_offset(); @@ -397,6 +672,11 @@ class Gitg.DiffViewFile : Gtk.Grid buffer_line++; } + if (lines.size != 0) + { + d_regions += region; + } + int line_hunk_start = iter.get_line(); buffer.insert(ref iter, (string)content.data, -1); diff --git a/libgitg/gitg-diff-view.vala b/libgitg/gitg-diff-view.vala index d8bfa3e1..0f2a5fa0 100644 --- a/libgitg/gitg-diff-view.vala +++ b/libgitg/gitg-diff-view.vala @@ -101,6 +101,8 @@ public class Gitg.DiffView : Gtk.Grid public bool use_gravatar { get; construct set; default = true; } public int tab_width { get; construct set; default = 4; } public bool handle_selection { get; construct set; default = false; } + public bool highlight { get; construct set; default = true; } + public Repository repository { get; set; } private bool flag_get(Ggit.DiffOption f) { @@ -325,7 +327,7 @@ public class Gitg.DiffView : Gtk.Grid add_file(); - current_file = new Gitg.DiffViewFile(delta, handle_selection); + current_file = new Gitg.DiffViewFile(repository, delta, handle_selection); return 0; }, diff --git a/plugins/diff/gitg-diff.vala b/plugins/diff/gitg-diff.vala index fb036179..2a294620 100644 --- a/plugins/diff/gitg-diff.vala +++ b/plugins/diff/gitg-diff.vala @@ -35,7 +35,10 @@ namespace GitgDiff construct { d_diff = new Gitg.DiffView(); + d_diff.show_parents = true; + d_diff.repository = application.repository; + d_diff.show(); var settings = new Settings("org.gnome.gitg.preferences.diff");