From 34657b821ae597de76ffd5a70d2b0b298dc270ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20D=C3=A1vila?= Date: Tue, 15 Dec 2015 18:09:09 -0500 Subject: [PATCH] Add syntax highlighting to diff view. #3945 --- app/helpers/application_helper.rb | 6 ++++ app/helpers/blob_helper.rb | 29 ++++++++++++++++--- .../projects/diffs/_parallel_view.html.haml | 8 +++-- app/views/projects/diffs/_text_file.html.haml | 6 ++-- lib/rouge/lexers/gitlab_diff.rb | 20 +++++++++++++ spec/helpers/blob_helper_spec.rb | 11 +++++++ 6 files changed, 71 insertions(+), 9 deletions(-) create mode 100644 lib/rouge/lexers/gitlab_diff.rb diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 0b00b9a0702..bc4b6ec0327 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -326,4 +326,10 @@ module ApplicationHelper def truncate_first_line(message, length = 50) truncate(message.each_line.first.chomp, length: length) if message end + + def unescape_html(content) + text = CGI.unescapeHTML(content) + text.gsub!(' ', ' ') + text + end end diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index d31d4cde08f..bf18673972c 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -1,11 +1,17 @@ module BlobHelper - def highlight(blob_name, blob_content, nowrap: false, continue: false) - @formatter ||= Rouge::Formatters::HTMLGitlab.new( - nowrap: nowrap, + def rouge_formatter(options = {}) + default_options = { + nowrap: false, cssclass: 'code highlight', lineanchors: true, lineanchorsid: 'LC' - ) + } + + Rouge::Formatters::HTMLGitlab.new(default_options.merge!(options)) + end + + def highlight(blob_name, blob_content, nowrap: false, continue: false) + @formatter ||= rouge_formatter(nowrap: nowrap) begin @lexer ||= Rouge::Lexer.guess(filename: blob_name, source: blob_content).new @@ -18,6 +24,21 @@ module BlobHelper result end + def highlight_line(blob_name, content, continue: false) + if @previous_blob_name != blob_name + @parent = Rouge::Lexer.guess(filename: blob_name, source: content).new rescue Rouge::Lexers::PlainText.new + @lexer = Rouge::Lexers::GitlabDiff.new(parent_lexer: @parent) + @options = Rouge::Lexers::PlainText === @parent ? {} : { continue: continue } + end + + @previous_blob_name = blob_name + @formatter ||= rouge_formatter(nowrap: true) + + content.sub!(/\A((?:\+|-)\s*)/, '') # Don't format '+' or '-' indicators. + + "#{$1}#{@formatter.format(@lexer.lex(content, @options))}".html_safe + end + def no_highlight_files %w(credits changelog news copying copyright license authors) end diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml index 37fd1b1ec8a..c6a9d71e789 100644 --- a/app/views/projects/diffs/_parallel_view.html.haml +++ b/app/views/projects/diffs/_parallel_view.html.haml @@ -1,5 +1,5 @@ / Side-by-side diff view -%div.text-file.diff-wrap-lines +%div.text-file.diff-wrap-lines.code.file-content.js-syntax-highlight{ class: user_color_scheme } %table - parallel_diff(diff_file, index).each do |line| - type_left = line[0] @@ -20,7 +20,8 @@ = link_to raw(line_number_left), "##{line_code_left}", id: line_code_left - if @comments_allowed && can?(current_user, :create_note, @project) = link_to_new_diff_note(line_code_left, 'old') - %td.line_content{class: "parallel noteable_line #{type_left} #{line_code_left}", "line_code" => line_code_left }= raw line_content_left + %td.line_content{class: "parallel noteable_line #{type_left} #{line_code_left}", "line_code" => line_code_left }< + = highlight_line(diff_file.new_path, unescape_html(line_content_left)) - if type_right == 'new' - new_line_class = 'new' @@ -33,7 +34,8 @@ = link_to raw(line_number_right), "##{new_line_code}", id: new_line_code - if @comments_allowed && can?(current_user, :create_note, @project) = link_to_new_diff_note(line_code_right, 'new') - %td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code}", "line_code" => new_line_code}= raw line_content_right + %td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code}", "line_code" => new_line_code}< + = highlight_line(diff_file.new_path, unescape_html(line_content_right)) - if @reply_allowed - comments_left, comments_right = organize_comments(type_left, type_right, line_code_left, line_code_right) diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml index 977ca423f75..78c66a6291e 100644 --- a/app/views/projects/diffs/_text_file.html.haml +++ b/app/views/projects/diffs/_text_file.html.haml @@ -3,7 +3,8 @@ .suppressed-container %a.show-suppressed-diff.js-show-suppressed-diff Changes suppressed. Click to show. -%table.text-file{class: "#{'hide' if too_big}"} +%table.text-file.code.js-syntax-highlight{ class: [user_color_scheme, too_big ? 'hide' : ''] } + - last_line = 0 - diff_file.diff_lines.each_with_index do |line, index| - type = line.type @@ -21,7 +22,8 @@ = link_to_new_diff_note(line_code) %td.new_line{data: {linenumber: line.new_pos}} = link_to raw(type == "old" ? " " : line.new_pos) , "##{line_code}", id: line_code - %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line.text) + %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}< + = highlight_line(diff_file.new_path, unescape_html(diff_line_content(line.text))) - if @reply_allowed - comments = @line_notes.select { |n| n.line_code == line_code && n.active? }.sort_by(&:created_at) diff --git a/lib/rouge/lexers/gitlab_diff.rb b/lib/rouge/lexers/gitlab_diff.rb new file mode 100644 index 00000000000..e136d47df00 --- /dev/null +++ b/lib/rouge/lexers/gitlab_diff.rb @@ -0,0 +1,20 @@ +Rouge::Token::Tokens.token(:InlineDiff, 'idiff') + +module Rouge + module Lexers + class GitlabDiff < RegexLexer + title "GitLab Diff" + tag 'gitlab_diff' + + state :root do + rule %r{(.*?)} do |match| + token InlineDiff, match[1] + end + + rule /(?:(?!puts 'Hello' world) + end + + it 'should respect the inline diff markup' do + result = highlight_line('demo.rb', "puts 'Hello' world") + expect(result).to eq(expected) + end + end end