Skip to content
This repository was archived by the owner on Nov 30, 2024. It is now read-only.

Commit 170b28e

Browse files
committed
Implement syntax highlighting.
1 parent edc8e4b commit 170b28e

File tree

5 files changed

+142
-3
lines changed

5 files changed

+142
-3
lines changed

lib/rspec/core/formatters/exception_presenter.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,8 @@ def read_failed_lines
204204

205205
file_path, line_number = matching_line.match(/(.+?):(\d+)(|:\d+)/)[1..2]
206206
max_line_count = RSpec.configuration.max_displayed_failure_line_count
207-
SnippetExtractor.extract_expression_lines_at(file_path, line_number.to_i, max_line_count)
207+
lines = SnippetExtractor.extract_expression_lines_at(file_path, line_number.to_i, max_line_count)
208+
RSpec.world.source_cache.syntax_highlighter.highlight(lines)
208209
rescue SnippetExtractor::NoSuchFileError
209210
["Unable to find #{file_path} to read failed line"]
210211
rescue SnippetExtractor::NoSuchLineError

lib/rspec/core/source.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
RSpec::Support.require_rspec_core 'source/node'
2+
RSpec::Support.require_rspec_core 'source/syntax_highlighter'
23
RSpec::Support.require_rspec_core 'source/token'
34

45
module RSpec
@@ -58,8 +59,11 @@ def inspect
5859

5960
# @private
6061
class Cache
61-
def initialize
62+
attr_reader :syntax_highlighter
63+
64+
def initialize(configuration)
6265
@sources_by_path = {}
66+
@syntax_highlighter = SyntaxHighlighter.new(configuration)
6367
end
6468

6569
def source_from_file(path)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
module RSpec
2+
module Core
3+
class Source
4+
class SyntaxHighlighter
5+
def initialize(configuration)
6+
@configuration = configuration
7+
end
8+
9+
def highlight(code)
10+
implementation.highlight_syntax(code)
11+
end
12+
13+
private
14+
15+
def implementation
16+
return color_enabled_implementation if @configuration.color_enabled?
17+
ColorDisabledImplementation
18+
end
19+
20+
def color_enabled_implementation
21+
@color_enabled_implementation ||= begin
22+
::Kernel.require 'coderay'
23+
CodeRayImplementation
24+
rescue LoadError
25+
NoCodeRayImplementation
26+
end
27+
end
28+
29+
module CodeRayImplementation
30+
RESET_CODE = "\e[0m"
31+
32+
def self.highlight_syntax(lines)
33+
highlighted = CodeRay.encode(lines.join("\n"), :ruby, :terminal)
34+
highlighted.split("\n").map do |line|
35+
line.sub(/\S/) { |char| char.insert(0, RESET_CODE) }
36+
end
37+
end
38+
end
39+
40+
module NoCodeRayImplementation
41+
INSTALL_CODERAY_COMMENT = ["# Install the coderay gem to get syntax highlighting"]
42+
43+
def self.highlight_syntax(lines)
44+
return lines unless lines.size > 1
45+
lines + INSTALL_CODERAY_COMMENT
46+
end
47+
end
48+
49+
module ColorDisabledImplementation
50+
def self.highlight_syntax(lines)
51+
lines
52+
end
53+
end
54+
end
55+
end
56+
end
57+
end

lib/rspec/core/world.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def reporter
111111
def source_cache
112112
@source_cache ||= begin
113113
RSpec::Support.require_rspec_core "source"
114-
Source::Cache.new
114+
Source::Cache.new(@configuration)
115115
end
116116
end
117117

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
require 'rspec/core/source/syntax_highlighter'
2+
3+
class RSpec::Core::Source
4+
RSpec.describe SyntaxHighlighter do
5+
let(:config) { RSpec::Core::Configuration.new.tap { |c| c.color = true } }
6+
let(:highlighter) { SyntaxHighlighter.new(config) }
7+
8+
def be_highlighted
9+
include("\e[32m")
10+
end
11+
12+
context "when CodeRay is available" do
13+
before { expect { require 'coderay' }.not_to raise_error }
14+
15+
it 'highlights the syntax of the provided lines' do
16+
highlighted = highlighter.highlight(['[:ok, "ok"]'])
17+
expect(highlighted.size).to eq(1)
18+
expect(highlighted.first).to be_highlighted.and include(":ok")
19+
end
20+
21+
it 'prefixes the each line with a reset escape code so it can be interpolated in a colored string without affecting the syntax highlighting of the snippet' do
22+
highlighted = highlighter.highlight(['a = 1', 'b = 2'])
23+
expect(highlighted).to all start_with("\e[0m")
24+
end
25+
26+
it 'leaves leading spaces alone so it can be re-indented as needed without the leading reset code interfering' do
27+
highlighted = highlighter.highlight([' a = 1', ' b = 2'])
28+
expect(highlighted).to all start_with(" \e[0m")
29+
end
30+
31+
it 'returns the provided lines unmodified if color is disabled' do
32+
config.color = false
33+
expect(highlighter.highlight(['[:ok, "ok"]'])).to eq(['[:ok, "ok"]'])
34+
end
35+
end
36+
37+
context "when CodeRay is unavailable" do
38+
before do
39+
allow(::Kernel).to receive(:require).with("coderay").and_raise(LoadError)
40+
end
41+
42+
it 'does not highlight the syntax' do
43+
unhighlighted = highlighter.highlight(['[:ok, "ok"]'])
44+
expect(unhighlighted.size).to eq(1)
45+
expect(unhighlighted.first).not_to be_highlighted
46+
end
47+
48+
it 'adds a comment explaining the user can get syntax highlighting by installing coderay' do
49+
lines = ["a = 1", "b = 2"]
50+
expect(highlighter.highlight(lines)).to eq([
51+
"a = 1",
52+
"b = 2",
53+
"# Install the coderay gem to get syntax highlighting"
54+
])
55+
end
56+
57+
it 'does not mutate the input array' do
58+
lines = ["a = 1", "b = 2"]
59+
expect { highlighter.highlight(lines) }.not_to change { lines }
60+
end
61+
62+
it 'does not add the comment about coderay if the snippet is only one line as we do not want to convert it to multiline just for the comment' do
63+
expect(highlighter.highlight(["a = 1"])).to eq(["a = 1"])
64+
end
65+
66+
it 'does not add the comment about coderay if given no lines' do
67+
expect(highlighter.highlight([])).to eq([])
68+
end
69+
70+
it 'does not add the comment about coderay if color id disabled even when given a multiline snippet' do
71+
config.color = false
72+
lines = ["a = 1", "b = 2"]
73+
expect(highlighter.highlight(lines)).to eq(lines)
74+
end
75+
end
76+
end
77+
end

0 commit comments

Comments
 (0)