diff options
author | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2011-03-11 20:23:29 +0000 |
---|---|---|
committer | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2011-03-11 20:23:29 +0000 |
commit | 21c79827ff73916aa7fa8a6cce0645fc34a7c7fd (patch) | |
tree | 0c85f6328ee655a5fe161e7037963656b1af1107 /lib/redmine/unified_diff.rb | |
parent | 099ba68836b9ff4544df3c46b6f5cb8a6081a185 (diff) | |
download | redmine-21c79827ff73916aa7fa8a6cce0645fc34a7c7fd.tar.gz redmine-21c79827ff73916aa7fa8a6cce0645fc34a7c7fd.zip |
Highlight changes inside diff lines (#7139).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@5094 e93f8b46-1217-0410-a6f0-8f06a7374b81
Diffstat (limited to 'lib/redmine/unified_diff.rb')
-rw-r--r-- | lib/redmine/unified_diff.rb | 170 |
1 files changed, 115 insertions, 55 deletions
diff --git a/lib/redmine/unified_diff.rb b/lib/redmine/unified_diff.rb index 430b1254a..f77721d6f 100644 --- a/lib/redmine/unified_diff.rb +++ b/lib/redmine/unified_diff.rb @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2008 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2011 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,14 +17,16 @@ module Redmine # Class used to parse unified diffs - class UnifiedDiff < Array + class UnifiedDiff < Array + attr_reader :diff_type + def initialize(diff, options={}) options.assert_valid_keys(:type, :max_lines) diff = diff.split("\n") if diff.is_a?(String) - diff_type = options[:type] || 'inline' + @diff_type = options[:type] || 'inline' lines = 0 @truncated = false - diff_table = DiffTable.new(diff_type) + diff_table = DiffTable.new(@diff_type) diff.each do |line| line_encoding = nil if line.respond_to?(:force_encoding) @@ -53,17 +55,15 @@ module Redmine end # Class that represents a file diff - class DiffTable < Hash - attr_reader :file_name, :line_num_l, :line_num_r + class DiffTable < Array + attr_reader :file_name # Initialize with a Diff file and the type of Diff View # The type view must be inline or sbs (side_by_side) def initialize(type="inline") @parsing = false - @nb_line = 1 - @start = false - @before = 'same' - @second = true + @added = 0 + @removed = 0 @type = type end @@ -86,11 +86,21 @@ module Redmine @line_num_l = $2.to_i @line_num_r = $5.to_i else - @nb_line += 1 if parse_line(line, @type) + parse_line(line, @type) end end return true end + + def each_line + prev_line_left, prev_line_right = nil, nil + each do |line| + spacing = prev_line_left && prev_line_right && (line.nb_line_left != prev_line_left+1) && (line.nb_line_right != prev_line_right+1) + yield spacing, line + prev_line_left = line.nb_line_left.to_i if line.nb_line_left.to_i > 0 + prev_line_right = line.nb_line_right.to_i if line.nb_line_right.to_i > 0 + end + end def inspect puts '### DIFF TABLE ###' @@ -100,74 +110,91 @@ module Redmine end end - private - # Test if is a Side By Side type - def sbs?(type, func) - if @start and type == "sbs" - if @before == func and @second - tmp_nb_line = @nb_line - self[tmp_nb_line] = Diff.new - else - @second = false - tmp_nb_line = @start - @start += 1 - @nb_line -= 1 - end - else - tmp_nb_line = @nb_line - @start = @nb_line - self[tmp_nb_line] = Diff.new - @second = true - end - unless self[tmp_nb_line] - @nb_line += 1 - self[tmp_nb_line] = Diff.new - else - self[tmp_nb_line] - end - end + private # Escape the HTML for the diff def escapeHTML(line) CGI.escapeHTML(line) end + + def diff_for_added_line + if @type == 'sbs' && @removed > 0 && @added < @removed + self[-(@removed - @added)] + else + diff = Diff.new + self << diff + diff + end + end def parse_line(line, type="inline") if line[0, 1] == "+" - diff = sbs? type, 'add' - @before = 'add' + diff = diff_for_added_line diff.line_right = escapeHTML line[1..-1] diff.nb_line_right = @line_num_r diff.type_diff_right = 'diff_in' @line_num_r += 1 + @added += 1 true elsif line[0, 1] == "-" - diff = sbs? type, 'remove' - @before = 'remove' - diff.line_left = escapeHTML line[1..-1] - diff.nb_line_left = @line_num_l - diff.type_diff_left = 'diff_out' - @line_num_l += 1 - true - elsif line[0, 1] =~ /\s/ - @before = 'same' - @start = false diff = Diff.new - diff.line_right = escapeHTML line[1..-1] - diff.nb_line_right = @line_num_r diff.line_left = escapeHTML line[1..-1] diff.nb_line_left = @line_num_l - self[@nb_line] = diff + diff.type_diff_left = 'diff_out' + self << diff @line_num_l += 1 - @line_num_r += 1 + @removed += 1 true - elsif line[0, 1] = "\\" + else + write_offsets + if line[0, 1] =~ /\s/ + diff = Diff.new + diff.line_right = escapeHTML line[1..-1] + diff.nb_line_right = @line_num_r + diff.line_left = escapeHTML line[1..-1] + diff.nb_line_left = @line_num_l + self << diff + @line_num_l += 1 + @line_num_r += 1 + true + elsif line[0, 1] = "\\" true else false end end end + + def write_offsets + if @added > 0 && @added == @removed + @added.times do |i| + line = self[-(1 + i)] + removed = (@type == 'sbs') ? line : self[-(1 + @added + i)] + offsets = offsets(removed.line_left, line.line_right) + removed.offsets = line.offsets = offsets + end + end + @added = 0 + @removed = 0 + end + + def offsets(line_left, line_right) + if line_left.present? && line_right.present? && line_left != line_right + max = [line_left.size, line_right.size].min + starting = 0 + while starting < max && line_left[starting] == line_right[starting] + starting += 1 + end + ending = -1 + while ending >= -(max - starting) && line_left[ending] == line_right[ending] + ending -= 1 + end + unless starting == 0 && ending == -1 + [starting, ending] + end + end + end + end # A line of diff class Diff @@ -177,6 +204,7 @@ module Redmine attr_accessor :line_right attr_accessor :type_diff_right attr_accessor :type_diff_left + attr_accessor :offsets def initialize() self.nb_line_left = '' @@ -186,6 +214,38 @@ module Redmine self.type_diff_right = '' self.type_diff_left = '' end + + def type_diff + type_diff_right == 'diff_in' ? type_diff_right : type_diff_left + end + + def line + type_diff_right == 'diff_in' ? line_right : line_left + end + + def html_line_left + if offsets + line_left.dup.insert(offsets.first, '<span>').insert(offsets.last, '</span>') + else + line_left + end + end + + def html_line_right + if offsets + line_right.dup.insert(offsets.first, '<span>').insert(offsets.last, '</span>') + else + line_right + end + end + + def html_line + if offsets + line.dup.insert(offsets.first, '<span>').insert(offsets.last, '</span>') + else + line + end + end def inspect puts '### Start Line Diff ###' |