##// END OF EJS Templates
Highlight changes inside diff lines (#7139)....
Jean-Philippe Lang -
r4974:21c79827ff73
parent child
Show More
@@ -0,0 +1,46
1 --- partials.txt Wed Jan 19 12:06:17 2011
2 +++ partials.1.txt Wed Jan 19 12:06:10 2011
3 @@ -1,31 +1,31 @@
4 -Lorem ipsum dolor sit amet, consectetur adipiscing elit
5 +Lorem ipsum dolor sit amet, consectetur adipiscing xx
6 Praesent et sagittis dui. Vivamus ac diam diam
7 -Ut sed auctor justo
8 +xxx auctor justo
9 Suspendisse venenatis sollicitudin magna quis suscipit
10 -Sed blandit gravida odio ac ultrices
11 +Sed blandit gxxxxa odio ac ultrices
12 Morbi rhoncus est ut est aliquam tempus
13 -Morbi id nisi vel felis tincidunt tempus
14 +Morbi id nisi vel felis xx tempus
15 Mauris auctor sagittis ante eu luctus
16 -Fusce commodo felis sed ligula congue molestie
17 +Fusce commodo felis sed ligula congue
18 Lorem ipsum dolor sit amet, consectetur adipiscing elit
19 -Praesent et sagittis dui. Vivamus ac diam diam
20 +et sagittis dui. Vivamus ac diam diam
21 Ut sed auctor justo
22 Suspendisse venenatis sollicitudin magna quis suscipit
23 Sed blandit gravida odio ac ultrices
24
25 -Lorem ipsum dolor sit amet, consectetur adipiscing elit
26 -Praesent et sagittis dui. Vivamus ac diam diam
27 +Lorem ipsum dolor sit amet, xxxx adipiscing elit
28 Ut sed auctor justo
29 Suspendisse venenatis sollicitudin magna quis suscipit
30 Sed blandit gravida odio ac ultrices
31 -Morbi rhoncus est ut est aliquam tempus
32 +Morbi rhoncus est ut est xxxx tempus
33 +New line
34 Morbi id nisi vel felis tincidunt tempus
35 Mauris auctor sagittis ante eu luctus
36 Fusce commodo felis sed ligula congue molestie
37
38 -Lorem ipsum dolor sit amet, consectetur adipiscing elit
39 -Praesent et sagittis dui. Vivamus ac diam diam
40 -Ut sed auctor justo
41 +Lorem ipsum dolor sit amet, xxxxtetur adipiscing elit
42 +Praesent et xxxxx. Vivamus ac diam diam
43 +Ut sed auctor
44 Suspendisse venenatis sollicitudin magna quis suscipit
45 Sed blandit gravida odio ac ultrices
46 Morbi rhoncus est ut est aliquam tempus
@@ -1,66 +1,56
1 <% diff = Redmine::UnifiedDiff.new(diff, :type => diff_type, :max_lines => Setting.diff_max_lines_displayed.to_i) -%>
1 <% diff = Redmine::UnifiedDiff.new(diff, :type => diff_type, :max_lines => Setting.diff_max_lines_displayed.to_i) -%>
2
2 <% diff.each do |table_file| -%>
3 <% diff.each do |table_file| -%>
3 <div class="autoscroll">
4 <div class="autoscroll">
4 <% if diff_type == 'sbs' -%>
5 <% if diff.diff_type == 'sbs' -%>
5 <table class="filecontent">
6 <table class="filecontent">
6 <thead>
7 <thead>
7 <tr><th colspan="4" class="filename"><%=to_utf8 table_file.file_name %></th></tr>
8 <tr><th colspan="4" class="filename"><%=to_utf8 table_file.file_name %></th></tr>
8 </thead>
9 </thead>
9 <tbody>
10 <tbody>
10 <% prev_line_left, prev_line_right = nil, nil -%>
11 <% table_file.each_line do |spacing, line| -%>
11 <% table_file.keys.sort.each do |key| -%>
12 <% if spacing -%>
12 <% if prev_line_left && prev_line_right && (table_file[key].nb_line_left != prev_line_left+1) && (table_file[key].nb_line_right != prev_line_right+1) -%>
13 <tr class="spacing">
13 <tr class="spacing">
14 <th class="line-num">...</th><td></td><th class="line-num">...</th><td></td>
14 <th class="line-num">...</th><td></td><th class="line-num">...</th><td></td>
15 </tr>
15 <% end -%>
16 <% end -%>
16 <tr>
17 <tr>
17 <th class="line-num"><%= table_file[key].nb_line_left %></th>
18 <th class="line-num"><%= line.nb_line_left %></th>
18 <td class="line-code <%= table_file[key].type_diff_left %>">
19 <td class="line-code <%= line.type_diff_left %>">
19 <pre><%=to_utf8 table_file[key].line_left %></pre>
20 <pre><%=to_utf8 line.html_line_left %></pre>
20 </td>
21 </td>
21 <th class="line-num"><%= table_file[key].nb_line_right %></th>
22 <th class="line-num"><%= line.nb_line_right %></th>
22 <td class="line-code <%= table_file[key].type_diff_right %>">
23 <td class="line-code <%= line.type_diff_right %>">
23 <pre><%=to_utf8 table_file[key].line_right %></pre>
24 <pre><%=to_utf8 line.html_line_right %></pre>
24 </td>
25 </td>
25 </tr>
26 </tr>
26 <% prev_line_left, prev_line_right = table_file[key].nb_line_left.to_i, table_file[key].nb_line_right.to_i -%>
27 <% end -%>
27 <% end -%>
28 </tbody>
28 </tbody>
29 </table>
29 </table>
30
30
31 <% else -%>
31 <% else -%>
32 <table class="filecontent syntaxhl">
32 <table class="filecontent">
33 <thead>
33 <thead>
34 <tr><th colspan="3" class="filename"><%=to_utf8 table_file.file_name %></th></tr>
34 <tr><th colspan="3" class="filename"><%=to_utf8 table_file.file_name %></th></tr>
35 </thead>
35 </thead>
36 <tbody>
36 <tbody>
37 <% prev_line_left, prev_line_right = nil, nil -%>
37 <% table_file.each_line do |spacing, line| %>
38 <% table_file.keys.sort.each do |key, line| %>
38 <% if spacing -%>
39 <% if prev_line_left && prev_line_right && (table_file[key].nb_line_left != prev_line_left+1) && (table_file[key].nb_line_right != prev_line_right+1) -%>
40 <tr class="spacing">
39 <tr class="spacing">
41 <th class="line-num">...</th><th class="line-num">...</th><td></td>
40 <th class="line-num">...</th><th class="line-num">...</th><td></td>
42 </tr>
41 </tr>
43 <% end -%>
42 <% end -%>
44 <tr>
43 <tr>
45 <th class="line-num"><%= table_file[key].nb_line_left %></th>
44 <th class="line-num"><%= line.nb_line_left %></th>
46 <th class="line-num"><%= table_file[key].nb_line_right %></th>
45 <th class="line-num"><%= line.nb_line_right %></th>
47 <% if table_file[key].line_left.empty? -%>
46 <td class="line-code <%= line.type_diff %>">
48 <td class="line-code <%= table_file[key].type_diff_right %>">
47 <pre><%=to_utf8 line.html_line %></pre>
49 <pre><%=to_utf8 table_file[key].line_right %></pre>
50 </td>
48 </td>
51 <% else -%>
52 <td class="line-code <%= table_file[key].type_diff_left %>">
53 <pre><%=to_utf8 table_file[key].line_left %></pre>
54 </td>
55 <% end -%>
56 </tr>
49 </tr>
57 <% prev_line_left = table_file[key].nb_line_left.to_i if table_file[key].nb_line_left.to_i > 0 -%>
58 <% prev_line_right = table_file[key].nb_line_right.to_i if table_file[key].nb_line_right.to_i > 0 -%>
59 <% end -%>
50 <% end -%>
60 </tbody>
51 </tbody>
61 </table>
52 </table>
62 <% end -%>
53 <% end -%>
63
64 </div>
54 </div>
65 <% end -%>
55 <% end -%>
66
56
@@ -1,5 +1,5
1 # redMine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
@@ -17,14 +17,16
17
17
18 module Redmine
18 module Redmine
19 # Class used to parse unified diffs
19 # Class used to parse unified diffs
20 class UnifiedDiff < Array
20 class UnifiedDiff < Array
21 attr_reader :diff_type
22
21 def initialize(diff, options={})
23 def initialize(diff, options={})
22 options.assert_valid_keys(:type, :max_lines)
24 options.assert_valid_keys(:type, :max_lines)
23 diff = diff.split("\n") if diff.is_a?(String)
25 diff = diff.split("\n") if diff.is_a?(String)
24 diff_type = options[:type] || 'inline'
26 @diff_type = options[:type] || 'inline'
25 lines = 0
27 lines = 0
26 @truncated = false
28 @truncated = false
27 diff_table = DiffTable.new(diff_type)
29 diff_table = DiffTable.new(@diff_type)
28 diff.each do |line|
30 diff.each do |line|
29 line_encoding = nil
31 line_encoding = nil
30 if line.respond_to?(:force_encoding)
32 if line.respond_to?(:force_encoding)
@@ -53,17 +55,15 module Redmine
53 end
55 end
54
56
55 # Class that represents a file diff
57 # Class that represents a file diff
56 class DiffTable < Hash
58 class DiffTable < Array
57 attr_reader :file_name, :line_num_l, :line_num_r
59 attr_reader :file_name
58
60
59 # Initialize with a Diff file and the type of Diff View
61 # Initialize with a Diff file and the type of Diff View
60 # The type view must be inline or sbs (side_by_side)
62 # The type view must be inline or sbs (side_by_side)
61 def initialize(type="inline")
63 def initialize(type="inline")
62 @parsing = false
64 @parsing = false
63 @nb_line = 1
65 @added = 0
64 @start = false
66 @removed = 0
65 @before = 'same'
66 @second = true
67 @type = type
67 @type = type
68 end
68 end
69
69
@@ -86,11 +86,21 module Redmine
86 @line_num_l = $2.to_i
86 @line_num_l = $2.to_i
87 @line_num_r = $5.to_i
87 @line_num_r = $5.to_i
88 else
88 else
89 @nb_line += 1 if parse_line(line, @type)
89 parse_line(line, @type)
90 end
90 end
91 end
91 end
92 return true
92 return true
93 end
93 end
94
95 def each_line
96 prev_line_left, prev_line_right = nil, nil
97 each do |line|
98 spacing = prev_line_left && prev_line_right && (line.nb_line_left != prev_line_left+1) && (line.nb_line_right != prev_line_right+1)
99 yield spacing, line
100 prev_line_left = line.nb_line_left.to_i if line.nb_line_left.to_i > 0
101 prev_line_right = line.nb_line_right.to_i if line.nb_line_right.to_i > 0
102 end
103 end
94
104
95 def inspect
105 def inspect
96 puts '### DIFF TABLE ###'
106 puts '### DIFF TABLE ###'
@@ -100,74 +110,91 module Redmine
100 end
110 end
101 end
111 end
102
112
103 private
113 private
104 # Test if is a Side By Side type
105 def sbs?(type, func)
106 if @start and type == "sbs"
107 if @before == func and @second
108 tmp_nb_line = @nb_line
109 self[tmp_nb_line] = Diff.new
110 else
111 @second = false
112 tmp_nb_line = @start
113 @start += 1
114 @nb_line -= 1
115 end
116 else
117 tmp_nb_line = @nb_line
118 @start = @nb_line
119 self[tmp_nb_line] = Diff.new
120 @second = true
121 end
122 unless self[tmp_nb_line]
123 @nb_line += 1
124 self[tmp_nb_line] = Diff.new
125 else
126 self[tmp_nb_line]
127 end
128 end
129
114
130 # Escape the HTML for the diff
115 # Escape the HTML for the diff
131 def escapeHTML(line)
116 def escapeHTML(line)
132 CGI.escapeHTML(line)
117 CGI.escapeHTML(line)
133 end
118 end
119
120 def diff_for_added_line
121 if @type == 'sbs' && @removed > 0 && @added < @removed
122 self[-(@removed - @added)]
123 else
124 diff = Diff.new
125 self << diff
126 diff
127 end
128 end
134
129
135 def parse_line(line, type="inline")
130 def parse_line(line, type="inline")
136 if line[0, 1] == "+"
131 if line[0, 1] == "+"
137 diff = sbs? type, 'add'
132 diff = diff_for_added_line
138 @before = 'add'
139 diff.line_right = escapeHTML line[1..-1]
133 diff.line_right = escapeHTML line[1..-1]
140 diff.nb_line_right = @line_num_r
134 diff.nb_line_right = @line_num_r
141 diff.type_diff_right = 'diff_in'
135 diff.type_diff_right = 'diff_in'
142 @line_num_r += 1
136 @line_num_r += 1
137 @added += 1
143 true
138 true
144 elsif line[0, 1] == "-"
139 elsif line[0, 1] == "-"
145 diff = sbs? type, 'remove'
146 @before = 'remove'
147 diff.line_left = escapeHTML line[1..-1]
148 diff.nb_line_left = @line_num_l
149 diff.type_diff_left = 'diff_out'
150 @line_num_l += 1
151 true
152 elsif line[0, 1] =~ /\s/
153 @before = 'same'
154 @start = false
155 diff = Diff.new
140 diff = Diff.new
156 diff.line_right = escapeHTML line[1..-1]
157 diff.nb_line_right = @line_num_r
158 diff.line_left = escapeHTML line[1..-1]
141 diff.line_left = escapeHTML line[1..-1]
159 diff.nb_line_left = @line_num_l
142 diff.nb_line_left = @line_num_l
160 self[@nb_line] = diff
143 diff.type_diff_left = 'diff_out'
144 self << diff
161 @line_num_l += 1
145 @line_num_l += 1
162 @line_num_r += 1
146 @removed += 1
163 true
147 true
164 elsif line[0, 1] = "\\"
148 else
149 write_offsets
150 if line[0, 1] =~ /\s/
151 diff = Diff.new
152 diff.line_right = escapeHTML line[1..-1]
153 diff.nb_line_right = @line_num_r
154 diff.line_left = escapeHTML line[1..-1]
155 diff.nb_line_left = @line_num_l
156 self << diff
157 @line_num_l += 1
158 @line_num_r += 1
159 true
160 elsif line[0, 1] = "\\"
165 true
161 true
166 else
162 else
167 false
163 false
168 end
164 end
169 end
165 end
170 end
166 end
167
168 def write_offsets
169 if @added > 0 && @added == @removed
170 @added.times do |i|
171 line = self[-(1 + i)]
172 removed = (@type == 'sbs') ? line : self[-(1 + @added + i)]
173 offsets = offsets(removed.line_left, line.line_right)
174 removed.offsets = line.offsets = offsets
175 end
176 end
177 @added = 0
178 @removed = 0
179 end
180
181 def offsets(line_left, line_right)
182 if line_left.present? && line_right.present? && line_left != line_right
183 max = [line_left.size, line_right.size].min
184 starting = 0
185 while starting < max && line_left[starting] == line_right[starting]
186 starting += 1
187 end
188 ending = -1
189 while ending >= -(max - starting) && line_left[ending] == line_right[ending]
190 ending -= 1
191 end
192 unless starting == 0 && ending == -1
193 [starting, ending]
194 end
195 end
196 end
197 end
171
198
172 # A line of diff
199 # A line of diff
173 class Diff
200 class Diff
@@ -177,6 +204,7 module Redmine
177 attr_accessor :line_right
204 attr_accessor :line_right
178 attr_accessor :type_diff_right
205 attr_accessor :type_diff_right
179 attr_accessor :type_diff_left
206 attr_accessor :type_diff_left
207 attr_accessor :offsets
180
208
181 def initialize()
209 def initialize()
182 self.nb_line_left = ''
210 self.nb_line_left = ''
@@ -186,6 +214,38 module Redmine
186 self.type_diff_right = ''
214 self.type_diff_right = ''
187 self.type_diff_left = ''
215 self.type_diff_left = ''
188 end
216 end
217
218 def type_diff
219 type_diff_right == 'diff_in' ? type_diff_right : type_diff_left
220 end
221
222 def line
223 type_diff_right == 'diff_in' ? line_right : line_left
224 end
225
226 def html_line_left
227 if offsets
228 line_left.dup.insert(offsets.first, '<span>').insert(offsets.last, '</span>')
229 else
230 line_left
231 end
232 end
233
234 def html_line_right
235 if offsets
236 line_right.dup.insert(offsets.first, '<span>').insert(offsets.last, '</span>')
237 else
238 line_right
239 end
240 end
241
242 def html_line
243 if offsets
244 line.dup.insert(offsets.first, '<span>').insert(offsets.last, '</span>')
245 else
246 line
247 end
248 end
189
249
190 def inspect
250 def inspect
191 puts '### Start Line Diff ###'
251 puts '### Start Line Diff ###'
@@ -672,7 +672,9 div.autocomplete ul li span.informal {
672
672
673 /***** Diff *****/
673 /***** Diff *****/
674 .diff_out { background: #fcc; }
674 .diff_out { background: #fcc; }
675 .diff_out span { background: #faa; }
675 .diff_in { background: #cfc; }
676 .diff_in { background: #cfc; }
677 .diff_in span { background: #afa; }
676
678
677 .text-diff {
679 .text-diff {
678 padding: 1em;
680 padding: 1em;
@@ -1,5 +1,5
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
@@ -34,6 +34,63 class Redmine::UnifiedDiffTest < ActiveSupport::TestCase
34 assert_equal 2, diff.size
34 assert_equal 2, diff.size
35 end
35 end
36
36
37 def test_inline_partials
38 diff = Redmine::UnifiedDiff.new(read_diff_fixture('partials.diff'))
39 assert_equal 1, diff.size
40 diff = diff.first
41 assert_equal 43, diff.size
42
43 assert_equal [51, -1], diff[0].offsets
44 assert_equal [51, -1], diff[1].offsets
45 assert_equal 'Lorem ipsum dolor sit amet, consectetur adipiscing <span>elit</span>', diff[0].html_line
46 assert_equal 'Lorem ipsum dolor sit amet, consectetur adipiscing <span>xx</span>', diff[1].html_line
47
48 assert_nil diff[2].offsets
49 assert_equal 'Praesent et sagittis dui. Vivamus ac diam diam', diff[2].html_line
50
51 assert_equal [0, -14], diff[3].offsets
52 assert_equal [0, -14], diff[4].offsets
53 assert_equal '<span>Ut sed</span> auctor justo', diff[3].html_line
54 assert_equal '<span>xxx</span> auctor justo', diff[4].html_line
55
56 assert_equal [13, -19], diff[6].offsets
57 assert_equal [13, -19], diff[7].offsets
58
59 assert_equal [24, -8], diff[9].offsets
60 assert_equal [24, -8], diff[10].offsets
61
62 assert_equal [37, -1], diff[12].offsets
63 assert_equal [37, -1], diff[13].offsets
64
65 assert_equal [0, -38], diff[15].offsets
66 assert_equal [0, -38], diff[16].offsets
67 end
68
69 def test_side_by_side_partials
70 diff = Redmine::UnifiedDiff.new(read_diff_fixture('partials.diff'), :type => 'sbs')
71 assert_equal 1, diff.size
72 diff = diff.first
73 assert_equal 32, diff.size
74
75 assert_equal [51, -1], diff[0].offsets
76 assert_equal 'Lorem ipsum dolor sit amet, consectetur adipiscing <span>elit</span>', diff[0].html_line_left
77 assert_equal 'Lorem ipsum dolor sit amet, consectetur adipiscing <span>xx</span>', diff[0].html_line_right
78
79 assert_nil diff[1].offsets
80 assert_equal 'Praesent et sagittis dui. Vivamus ac diam diam', diff[1].html_line_left
81 assert_equal 'Praesent et sagittis dui. Vivamus ac diam diam', diff[1].html_line_right
82
83 assert_equal [0, -14], diff[2].offsets
84 assert_equal '<span>Ut sed</span> auctor justo', diff[2].html_line_left
85 assert_equal '<span>xxx</span> auctor justo', diff[2].html_line_right
86
87 assert_equal [13, -19], diff[4].offsets
88 assert_equal [24, -8], diff[6].offsets
89 assert_equal [37, -1], diff[8].offsets
90 assert_equal [0, -38], diff[10].offsets
91
92 end
93
37 def test_line_starting_with_dashes
94 def test_line_starting_with_dashes
38 diff = Redmine::UnifiedDiff.new(<<-DIFF
95 diff = Redmine::UnifiedDiff.new(<<-DIFF
39 --- old.txt Wed Nov 11 14:24:58 2009
96 --- old.txt Wed Nov 11 14:24:58 2009
General Comments 0
You need to be logged in to leave comments. Login now