Skip to content
Snippets Groups Projects
Commit 8dfad143 authored by Douwe Maan's avatar Douwe Maan
Browse files

Add inline diff markers in highlighted diffs.

parent 83e4fc18
No related branches found
No related tags found
1 merge request!2109Add syntax highlighting to diff view
Pipeline #
Loading
@@ -66,7 +66,7 @@ class Projects::BlobController < Projects::ApplicationController
Loading
@@ -66,7 +66,7 @@ class Projects::BlobController < Projects::ApplicationController
   
def diff def diff
@form = UnfoldForm.new(params) @form = UnfoldForm.new(params)
@lines = Gitlab::Diff::Highlight.process_file(repository, @ref, @path) @lines = Gitlab::Highlight.highlight_lines(repository, @ref, @path)
@lines = @lines[@form.since - 1..@form.to - 1] @lines = @lines[@form.since - 1..@form.to - 1]
   
if @form.bottom? if @form.bottom?
Loading
Loading
Loading
@@ -13,7 +13,7 @@
Loading
@@ -13,7 +13,7 @@
= link_to raw(left[:number]), "##{left[:line_code]}", id: left[:line_code] = link_to raw(left[:number]), "##{left[:line_code]}", id: left[:line_code]
- if @comments_allowed && can?(current_user, :create_note, @project) - if @comments_allowed && can?(current_user, :create_note, @project)
= link_to_new_diff_note(left[:line_code], 'old') = link_to_new_diff_note(left[:line_code], 'old')
%td.line_content{class: "parallel noteable_line #{left[:type]} #{left[:line_code]}", "line_code" => left[:line_code] }= diff_line_content(left[:text]) %td.line_content{class: "parallel noteable_line #{left[:type]} #{left[:line_code]}", data: { "line_code" => left[:line_code] }}= diff_line_content(left[:text])
   
- if right[:type] == 'new' - if right[:type] == 'new'
- new_line_class = 'new' - new_line_class = 'new'
Loading
@@ -26,7 +26,7 @@
Loading
@@ -26,7 +26,7 @@
= link_to raw(right[:number]), "##{new_line_code}", id: new_line_code = link_to raw(right[:number]), "##{new_line_code}", id: new_line_code
- if @comments_allowed && can?(current_user, :create_note, @project) - if @comments_allowed && can?(current_user, :create_note, @project)
= link_to_new_diff_note(right[:line_code], 'new') = link_to_new_diff_note(right[:line_code], 'new')
%td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code}", "line_code" => new_line_code}= diff_line_content(right[:text]) %td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code}", data: { "line_code" => new_line_code }}= diff_line_content(right[:text])
   
- if @reply_allowed - if @reply_allowed
- comments_left, comments_right = organize_comments(left[:type], right[:type], left[:line_code], right[:line_code]) - comments_left, comments_right = organize_comments(left[:type], right[:type], left[:line_code], right[:line_code])
Loading
Loading
Loading
@@ -22,7 +22,7 @@
Loading
@@ -22,7 +22,7 @@
= link_to_new_diff_note(line_code) = link_to_new_diff_note(line_code)
%td.new_line{data: {linenumber: line.new_pos}} %td.new_line{data: {linenumber: line.new_pos}}
= link_to raw(type == "old" ? "&nbsp;" : line.new_pos), "##{line_code}", id: line_code = link_to raw(type == "old" ? "&nbsp;" : line.new_pos), "##{line_code}", id: line_code
%td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= diff_line_content(line.text) %td.line_content{class: "noteable_line #{type} #{line_code}", data: { "line_code" => line_code }}= diff_line_content(line.text)
   
- if @reply_allowed - if @reply_allowed
- comments = @line_notes.select { |n| n.line_code == line_code && n.active? }.sort_by(&:created_at) - comments = @line_notes.select { |n| n.line_code == line_code && n.active? }.sort_by(&:created_at)
Loading
Loading
Loading
@@ -18,7 +18,7 @@ module Gitlab
Loading
@@ -18,7 +18,7 @@ module Gitlab
end end
   
def highlighted_diff_lines def highlighted_diff_lines
Gitlab::Diff::Highlight.process_diff_lines(self) Gitlab::Diff::Highlight.new(self).highlight
end end
   
def mode_changed? def mode_changed?
Loading
Loading
Loading
@@ -6,59 +6,199 @@ module Gitlab
Loading
@@ -6,59 +6,199 @@ module Gitlab
delegate :repository, :old_path, :new_path, :old_ref, :new_ref, delegate :repository, :old_path, :new_path, :old_ref, :new_ref,
to: :diff_file, prefix: :diff to: :diff_file, prefix: :diff
   
# Apply syntax highlight to provided source code def initialize(diff_file)
# @diff_file = diff_file
# diff_file - an instance of Gitlab::Diff::File @diff_lines = diff_file.diff_lines
# @raw_lines = @diff_lines.map(&:text)
# Returns an Array with the processed items.
def self.process_diff_lines(diff_file)
processor = new(diff_file)
processor.highlight
end end
   
def self.process_file(repository, ref, file_name) def highlight
blob = repository.blob_at(ref, file_name) return [] if @diff_lines.empty?
return [] unless blob
   
Gitlab::Highlight.highlight(file_name, blob.data).lines.map!(&:html_safe) find_inline_diffs
end
   
def initialize(diff_file) process_lines
@diff_file = diff_file
@file_name = diff_file.new_path @diff_lines
@lines = diff_file.diff_lines
end end
   
def highlight private
return [] if @lines.empty?
   
@lines.each_with_index do |line, i| def find_inline_diffs
line_prefix = line.text.match(/\A([+-])/) ? $1 : ' ' @inline_diffs = []
   
local_edit_indexes.each do |index|
old_index = index
new_index = index + 1
old_line = @raw_lines[old_index][1..-1]
new_line = @raw_lines[new_index][1..-1]
# Skip inline diff if empty line was replaced with content
next if old_line == ""
lcp = longest_common_prefix(old_line, new_line)
lcs = longest_common_suffix(old_line, new_line)
old_diff_range = lcp..(old_line.length - lcs - 1)
new_diff_range = lcp..(new_line.length - lcs - 1)
@inline_diffs[old_index] = old_diff_range if old_diff_range.begin <= old_diff_range.end
@inline_diffs[new_index] = new_diff_range if new_diff_range.begin <= new_diff_range.end
end
end
def process_lines
@diff_lines.each_with_index do |diff_line, i|
# ignore highlighting for "match" lines # ignore highlighting for "match" lines
next if line.type == 'match' next if diff_line.type == 'match'
rich_line = highlight_line(diff_line, i)
rich_line = mark_inline_diffs(rich_line, diff_line, i)
diff_line.text = rich_line.html_safe
end
end
def highlight_line(diff_line, index)
line_prefix = line_prefixes[index]
case diff_line.type
when 'new', nil
rich_line = new_lines[diff_line.new_pos - 1]
when 'old'
rich_line = old_lines[diff_line.old_pos - 1]
end
# Only update text if line is found. This will prevent
# issues with submodules given the line only exists in diff content.
rich_line ? line_prefix + rich_line : diff_line.text
end
def mark_inline_diffs(rich_line, diff_line, index)
inline_diff = @inline_diffs[index]
return rich_line unless inline_diff
raw_line = diff_line.text
# Based on the prefixless versions
from = inline_diff.begin + 1
to = inline_diff.end + 1
position_mapping = map_character_positions(raw_line, rich_line)
inline_diff_positions = position_mapping[from..to]
marker_ranges = collapse_ranges(inline_diff_positions)
offset = 0
marker_ranges.each do |range|
offset = insert_around_range(rich_line, range, "<span class='idiff'>", "</span>", offset)
end
rich_line
end
   
case line.type def line_prefixes
when 'new', nil @line_prefixes ||= @raw_lines.map { |line| line.match(/\A([+-])/) ? $1 : ' ' }
highlighted_line = new_lines[line.new_pos - 1] end
when 'old'
highlighted_line = old_lines[line.old_pos - 1] def local_edit_indexes
@local_edit_indexes ||= begin
joined_line_prefixes = " #{line_prefixes.join} "
offset = 0
local_edit_indexes = []
while index = joined_line_prefixes.index(" -+ ", offset)
local_edit_indexes << index
offset = index + 1
end end
   
# Only update text if line is found. This will prevent local_edit_indexes
# issues with submodules given the line only exists in diff content.
line.text = highlighted_line.insert(0, line_prefix).html_safe if highlighted_line
end end
end
def map_character_positions(raw_line, rich_line)
mapping = []
raw_pos = 0
rich_pos = 0
(0..raw_line.length).each do |raw_pos|
raw_char = raw_line[raw_pos]
rich_char = rich_line[rich_pos]
while rich_char == '<'
until rich_char == '>'
rich_pos += 1
rich_char = rich_line[rich_pos]
end
rich_pos += 1
rich_char = rich_line[rich_pos]
end
   
@lines mapping[raw_pos] = rich_pos
rich_pos += 1
end
mapping
end end
   
def old_lines def old_lines
@old_lines ||= self.class.process_file(diff_repository, diff_old_ref, diff_old_path) @old_lines ||= Gitlab::Highlight.highlight_lines(diff_repository, diff_old_ref, diff_old_path)
end end
   
def new_lines def new_lines
@new_lines ||= self.class.process_file(diff_repository, diff_new_ref, diff_new_path) @new_lines ||= Gitlab::Highlight.highlight_lines(diff_repository, diff_new_ref, diff_new_path)
end
def longest_common_suffix(a, b)
longest_common_prefix(a.reverse, b.reverse)
end
def longest_common_prefix(a, b)
max_length = [a.length, b.length].max
length = 0
(0..max_length - 1).each do |pos|
old_char = a[pos]
new_char = b[pos]
break if old_char != new_char
length += 1
end
length
end
def collapse_ranges(positions)
return [] if positions.empty?
ranges = []
start = prev = positions[0]
range = start..prev
positions[1..-1].each do |pos|
if pos == prev + 1
range = start..pos
prev = pos
else
ranges << range
start = prev = pos
range = start..prev
end
end
ranges << range
ranges
end
def insert_around_range(text, range, before, after, offset = 0)
from = range.begin
to = range.end
text.insert(offset + from, before)
offset += before.length
text.insert(offset + to + 1, after)
offset += after.length
offset
end end
end end
end end
Loading
Loading
Loading
@@ -7,6 +7,13 @@ module Gitlab
Loading
@@ -7,6 +7,13 @@ module Gitlab
formatter.format(lexer.lex(blob_content, continue: continue)).html_safe formatter.format(lexer.lex(blob_content, continue: continue)).html_safe
end end
   
def self.highlight_lines(repository, ref, file_name)
blob = repository.blob_at(ref, file_name)
return [] unless blob
highlight(file_name, blob.data).lines.map!(&:html_safe)
end
private private
   
def self.rouge_formatter(options = {}) def self.rouge_formatter(options = {})
Loading
Loading
Loading
@@ -51,9 +51,9 @@ describe Gitlab::Diff::Highlight, lib: true do
Loading
@@ -51,9 +51,9 @@ describe Gitlab::Diff::Highlight, lib: true do
end end
end end
   
describe '.process_file' do describe '.highlight_lines' do
let(:lines) do let(:lines) do
Gitlab::Diff::Highlight.process_file(project.repository, commit.id, 'files/ruby/popen.rb') Gitlab::Diff::Highlight.highlight_lines(project.repository, commit.id, 'files/ruby/popen.rb')
end end
   
it 'should properly highlight all the lines' do it 'should properly highlight all the lines' do
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment