From d0fb3b86481a318d4a2b3448333f5f04e7e2b281 Mon Sep 17 00:00:00 2001 From: Dan McClain Date: Sun, 22 Mar 2015 21:29:38 -0400 Subject: [PATCH 1/2] Implements highlighting At the end of the filename or language add `{1,2,-4,+5}`, `+` signifies that the line should be marked as added `-` signifies that the line should be marked as removed line numbers are otherwise highlighted Does not currently handle ranges Also updates colors in code blocks, fixing contrast --- lib/highlighter.rb | 88 +++++++++++++++++++++------ source/stylesheets/highlight.css.scss | 56 +++++++++++++++-- spec/highlighter_spec.rb | 86 +++++++++++++++++++++++--- 3 files changed, 198 insertions(+), 32 deletions(-) diff --git a/lib/highlighter.rb b/lib/highlighter.rb index e7f10e7e7..147e362d7 100644 --- a/lib/highlighter.rb +++ b/lib/highlighter.rb @@ -1,5 +1,6 @@ require "redcarpet" require "coderay" +require "nokogiri" module Highlighter class MissingLanguageError < StandardError; end @@ -17,42 +18,68 @@ def _highlight(string, language, class_name=nil) raise MissingLanguageError, error_message if language.nil? - language, file_name = _detect_language_and_filename(language) + language, file_name, changes = _detect_language_filename_and_changes(language) result = %Q{
} result += '
' result += '
' - result += _code_table(string, language, file_name) + result += _code_table(string, language, file_name, changes) result += '
' result += %Q{
} result end - def _detect_language_and_filename(language) - + def _detect_language_filename_and_changes(language) file_name = nil + changes = [] + changes_regex = /\{(.+)\}$/ bare_language_regex = /\A\w+\z/ + if change_numbers = changes_regex.match(language) + language = language.sub(changes_regex, '') + + changes = _parse_changes(change_numbers[1]) + end + unless language =~ bare_language_regex file_name = language - language = case /\.(\w+)$/.match(language)[1] - when 'hbs' - 'handlebars' - when 'js' - 'javascript' - when 'html' - 'html' - when 'css' - 'css' - when 'json' - 'json' - end + language = _determine_language(language) + end + [language, file_name, changes] + end + + def _determine_language(language) + case /\.(\w+)$/.match(language)[1] + when 'hbs' + 'handlebars' + when 'js' + 'javascript' + when 'html' + 'html' + when 'css' + 'css' + when 'json' + 'json' + end + end + + def _parse_changes(change_numbers) + changes = change_numbers.split(',').map do |change| + state = case change.slice(0) + when '+' + 'added' + when '-' + 'removed' + else + nil + end + + [change.to_i.abs, state] end - [language, file_name] end - def _code_table(string, language, file_name) + def _code_table(string, language, file_name, changes) code = CodeRay.scan(string, language) table = code.div css: :class, @@ -71,7 +98,30 @@ def _code_table(string, language, file_name) HEADER end - table + _highlight_lines(table, changes) + end + + def _highlight_lines(table, highlights) + return table if highlights.empty? + + table_xml = Nokogiri.XML(table) + + numbers_node = table_xml.at_xpath('//td[@class="line-numbers"]/pre') + code_node = table_xml.at_xpath('//td[@class="code"]/pre') + + numbers_contents = numbers_node.inner_html.split("\n") + code_contents = code_node.inner_html.split("\n") + + highlights.each do |line_number, state| + index = line_number - 1 + numbers_contents[index] = "#{numbers_contents[index]}" + code_contents[index] = "#{code_contents[index]}" + end + + numbers_node.inner_html = numbers_contents.join("\n") + code_node.inner_html = code_contents.join("\n") + + table_xml.to_xml end def highlight(language, class_name, &block) diff --git a/source/stylesheets/highlight.css.scss b/source/stylesheets/highlight.css.scss index 902e0cb3e..1a1d24ff8 100644 --- a/source/stylesheets/highlight.css.scss +++ b/source/stylesheets/highlight.css.scss @@ -4,12 +4,19 @@ $border-radius: 5px; $blue: #1f58ce; -$gray: #777777; -$green: #007700; +$gray: #545454; +$textColor: #222; +$green: #005f00; +$orange: #8f4a2c; + +$highlight-yellow: #f8eec7; +$highlight-green: #E8F4E5; +$highlight-red: #ffecec; body.guides .highlight { @include border-radius($border-radius); border: 1px solid #d1d1d1; + color: $textColor; } #content .highlight { @@ -46,6 +53,45 @@ body.guides .highlight { &.handlebars .ribbon { @include hidpi('handlebars-ribbon', png); } + + .highlight-line { + display: inline-block; + margin: 0 -10px; + background-color: $highlight-yellow; + border-right: $highlight-yellow solid 5px; + border-left: $highlight-yellow solid 5px; + box-sizing: content-box; + + &.added { + border-color: $highlight-green; + background-color: $highlight-green; + } + + &.removed { + border-color: $highlight-red; + background-color: $highlight-red; + } + } + + .code .highlight-line { + width: 613px; + margin: 0 -13px; + border-left-width: 13px; + border-right-width: 13px; + } + + .line-numbers .highlight-line { + width: 28px; + border-right-color: darken($highlight-yellow, 20%); + + &.added { + border-right-color: darken($highlight-green, 20%); + } + + &.removed { + border-right-color: darken($highlight-red, 20%); + } + } } .CodeRay { @@ -55,7 +101,7 @@ body.guides .highlight { text-align: center; border-right: 1px solid #d1d1d1; background-color: #f6f6f6; - color: #b4b4b4; + color: $gray; @include border-top-left-radius($border-radius); @include border-bottom-left-radius($border-radius); } @@ -71,7 +117,7 @@ body.guides .highlight { } .comment { - color: $gray; + color: $green; } .attribute-name { @@ -87,7 +133,7 @@ body.guides .highlight { } .keyword { - color: #ce791f; + color: $orange; } .key, .function { diff --git a/spec/highlighter_spec.rb b/spec/highlighter_spec.rb index ca4d6b8ca..007252294 100644 --- a/spec/highlighter_spec.rb +++ b/spec/highlighter_spec.rb @@ -12,37 +12,65 @@ class HelperTester Object.send :remove_const, :HelperTester end - describe '#_detect_language_and_filename' do + describe '#_detect_language_filename_and_changes' do it 'returns bare languages and does not return a filename' do - language, filename = HelperTester.new._detect_language_and_filename('javascript') + language, filename, changes = HelperTester.new._detect_language_filename_and_changes('javascript') expect(language).to eq 'javascript' expect(filename).to be_nil + expect(changes).to eq [] - language, filename = HelperTester.new._detect_language_and_filename('handlebars') + language, filename, changes = HelperTester.new._detect_language_filename_and_changes('handlebars') expect(language).to eq 'handlebars' expect(filename).to be_nil + expect(changes).to eq [] end it 'returns the detected language and the filename from a filename' do - language, filename = HelperTester.new._detect_language_and_filename('app/components/my-component.js') + language, filename, changes = HelperTester.new._detect_language_filename_and_changes('app/components/my-component.js') expect(language).to eq 'javascript' expect(filename).to eq 'app/components/my-component.js' + expect(changes).to eq [] - language, filename = HelperTester.new._detect_language_and_filename('app/templates/application.hbs') + language, filename, changes = HelperTester.new._detect_language_filename_and_changes('app/templates/application.hbs') expect(language).to eq 'handlebars' expect(filename).to eq 'app/templates/application.hbs' + expect(changes).to eq [] - language, filename = HelperTester.new._detect_language_and_filename('app/index.html') + language, filename, changes = HelperTester.new._detect_language_filename_and_changes('app/index.html') expect(language).to eq 'html' expect(filename).to eq 'app/index.html' + expect(changes).to eq [] - language, filename = HelperTester.new._detect_language_and_filename('app/styles/app.css') + language, filename, changes = HelperTester.new._detect_language_filename_and_changes('app/styles/app.css') expect(language).to eq 'css' expect(filename).to eq 'app/styles/app.css' + expect(changes).to eq [] - language, filename = HelperTester.new._detect_language_and_filename('bower.json') + language, filename, changes = HelperTester.new._detect_language_filename_and_changes('bower.json') expect(language).to eq 'json' expect(filename).to eq 'bower.json' + expect(changes).to eq [] + end + + it 'returns detected code changes' do + _, _, changes = HelperTester.new._detect_language_filename_and_changes('app/components/my-component.js{1,+3,4,-5,+6,-7}') + + expect(changes).to eq [ + [1, nil], + [3, 'added'], + [4, nil], + [5, 'removed'], + [6, 'added'], + [7, 'removed'], + ] + + _, _, changes = HelperTester.new._detect_language_filename_and_changes('javascript{+2,3,-5}') + + expect(changes).to eq [ + [2, 'added'], + [3, nil], + [5, 'removed'] + ] end end @@ -82,6 +110,48 @@ class HelperTester
export default Ember.Component.extend()
+OUTPUT + end + + it 'returns a code block with a filename in the table when using a filename fence with highlighting' do + code_block = HelperTester.new._highlight("export default Ember.Component.extend()\nvar added;\nvar removed;", + 'app/components/my-foo.js{1,+2,-3}') + expect(code_block).to eq <<-OUTPUT.sub(/\n$/, '') +
+ + + + + + + + + +
app/components/my-foo.js
1
+2
+3
export default Ember.Component.extend()
+var added;
+var removed;
+
+OUTPUT + end + + it 'returns a code block with a filename in the table when using a filename fence with highlighting' do + code_block = HelperTester.new._highlight("export default Ember.Component.extend()\nvar added;\nvar removed;", + 'javascript{1,+2,-3}') + expect(code_block).to eq <<-OUTPUT.sub(/\n$/, '') +
+ + + + + +
1
+2
+3
export default Ember.Component.extend()
+var added;
+var removed;
+
OUTPUT end end From 3b28c8c51b1f127f66edfd044c5c77e2deeacbac Mon Sep 17 00:00:00 2001 From: Dan McClain Date: Tue, 24 Mar 2015 16:37:53 -0400 Subject: [PATCH 2/2] WIP: Custom encoder --- Gemfile | 3 ++- Gemfile.lock | 16 ++++++++-------- lib/highlighter.rb | 13 +++++++++++-- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/Gemfile b/Gemfile index 8ac2f0b00..452797cb9 100644 --- a/Gemfile +++ b/Gemfile @@ -4,8 +4,9 @@ gem "redcarpet" gem "activesupport" gem "highline" gem "rake" -gem "coderay", :git => "git://github.com/dgeb/coderay.git", :branch => "handlebars" +gem "coderay" gem "middleman", '~> 3.0' +gem "byebug" gem "thin" gem "rack" gem "listen" diff --git a/Gemfile.lock b/Gemfile.lock index 151040176..405249871 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,13 +7,6 @@ GIT nokogiri swiftype (>= 0.0.4) -GIT - remote: git://github.com/dgeb/coderay.git - revision: 1362fd4af63331aed51ce9907ad7295cea228874 - branch: handlebars - specs: - coderay (1.1.0.rc1) - GEM remote: https://rubygems.org/ remote: https://rails-assets.org/ @@ -25,6 +18,9 @@ GEM thread_safe (~> 0.1) tzinfo (~> 1.1) builder (3.2.2) + byebug (4.0.1) + columnize (= 0.9.0) + rb-readline (= 0.5.2) capybara (2.4.4) mime-types (>= 1.16) nokogiri (>= 1.3.3) @@ -35,10 +31,12 @@ GEM timers (~> 4.0.0) chunky_png (1.3.3) cliver (0.3.2) + coderay (1.1.0) coffee-script (2.3.0) coffee-script-source execjs coffee-script-source (1.9.0) + columnize (0.9.0) compass (1.0.3) chunky_png (~> 1.2) compass-core (~> 1.0.2) @@ -142,6 +140,7 @@ GEM rb-fsevent (0.9.4) rb-inotify (0.9.5) ffi (>= 0.5.0) + rb-readline (0.5.2) redcarpet (3.2.2) rspec (3.2.0) rspec-core (~> 3.2.0) @@ -202,8 +201,9 @@ PLATFORMS DEPENDENCIES activesupport builder + byebug capybara - coderay! + coderay hashie highline listen diff --git a/lib/highlighter.rb b/lib/highlighter.rb index 147e362d7..a52ac69bb 100644 --- a/lib/highlighter.rb +++ b/lib/highlighter.rb @@ -1,7 +1,14 @@ require "redcarpet" +require "byebug" require "coderay" require "nokogiri" +class EmberEncoder < CodeRay::Encoders::Div + FILE_EXTENSION = 'ember.html' + register_for :ember + +end + module Highlighter class MissingLanguageError < StandardError; end class << self @@ -82,9 +89,11 @@ def _parse_changes(change_numbers) def _code_table(string, language, file_name, changes) code = CodeRay.scan(string, language) - table = code.div css: :class, + # debugger + table = code.ember css: :class, line_numbers: :table, - line_number_anchors: false + line_number_anchors: false, + highlight_lines: [1,3] if file_name.present?