diff --git a/better_errors.gemspec b/better_errors.gemspec
index 60a14810..a6cae6d3 100644
--- a/better_errors.gemspec
+++ b/better_errors.gemspec
@@ -31,7 +31,7 @@ Gem::Specification.new do |s|
# simplecov and coveralls must not be included here. See the Gemfiles instead.
s.add_dependency "erubi", ">= 1.0.0"
- s.add_dependency "coderay", ">= 1.0.0"
+ s.add_dependency "rouge", ">= 1.0.0"
s.add_dependency "rack", ">= 0.9.0"
# optional dependencies:
diff --git a/lib/better_errors.rb b/lib/better_errors.rb
index 346a1b1d..aff646e1 100644
--- a/lib/better_errors.rb
+++ b/lib/better_errors.rb
@@ -1,6 +1,5 @@
require "pp"
require "erubi"
-require "coderay"
require "uri"
require "better_errors/version"
diff --git a/lib/better_errors/code_formatter.rb b/lib/better_errors/code_formatter.rb
index a0e0245c..29092af9 100644
--- a/lib/better_errors/code_formatter.rb
+++ b/lib/better_errors/code_formatter.rb
@@ -4,14 +4,6 @@ class CodeFormatter
require "better_errors/code_formatter/html"
require "better_errors/code_formatter/text"
- FILE_TYPES = {
- ".rb" => :ruby,
- "" => :ruby,
- ".html" => :html,
- ".erb" => :erb,
- ".haml" => :haml
- }
-
attr_reader :filename, :line, :context
def initialize(filename, line, context = 5)
@@ -26,13 +18,21 @@ def output
source_unavailable
end
- def formatted_code
- formatted_lines.join
+ def line_range
+ min = [line - context, 1].max
+ max = [line + context, source_lines.count].min
+ min..max
end
- def coderay_scanner
- ext = File.extname(filename)
- FILE_TYPES[ext] || :text
+ def context_lines
+ range = line_range
+ source_lines[(range.begin - 1)..(range.end - 1)] or raise Errno::EINVAL
+ end
+
+ private
+
+ def formatted_code
+ formatted_lines.join
end
def each_line_of(lines, &blk)
@@ -41,23 +41,12 @@ def each_line_of(lines, &blk)
}
end
- def highlighted_lines
- CodeRay.scan(context_lines.join, coderay_scanner).html(css: :class).lines
- end
-
- def context_lines
- range = line_range
- source_lines[(range.begin - 1)..(range.end - 1)] or raise Errno::EINVAL
+ def source
+ @source ||= File.read(filename)
end
def source_lines
- @source_lines ||= File.readlines(filename)
- end
-
- def line_range
- min = [line - context, 1].max
- max = [line + context, source_lines.count].min
- min..max
+ @source_lines ||= source.lines
end
end
end
diff --git a/lib/better_errors/code_formatter/html.rb b/lib/better_errors/code_formatter/html.rb
index c20b3aa8..3e94cd4b 100644
--- a/lib/better_errors/code_formatter/html.rb
+++ b/lib/better_errors/code_formatter/html.rb
@@ -1,3 +1,5 @@
+require "rouge"
+
module BetterErrors
# @private
class CodeFormatter::HTML < CodeFormatter
@@ -8,7 +10,7 @@ def source_unavailable
def formatted_lines
each_line_of(highlighted_lines) { |highlight, current_line, str|
class_name = highlight ? "highlight" : ""
- sprintf '
%s
', class_name, str
+ sprintf '%s
', class_name, str
}
end
@@ -20,7 +22,19 @@ def formatted_nums
end
def formatted_code
- %{#{formatted_nums.join}
#{super}
}
+ %{
+ #{formatted_nums.join}
+
+ }
+ end
+
+ def rouge_lexer
+ Rouge::Lexer.guess(filename: filename, source: source) { Rouge::Lexers::Ruby }
end
+
+ def highlighted_lines
+ Rouge::Formatters::HTML.new.format(rouge_lexer.lex(context_lines.join)).lines
+ end
+
end
end
diff --git a/lib/better_errors/error_page.rb b/lib/better_errors/error_page.rb
index 7d5e08af..afb28f0d 100644
--- a/lib/better_errors/error_page.rb
+++ b/lib/better_errors/error_page.rb
@@ -1,6 +1,7 @@
require "cgi"
require "json"
require "securerandom"
+require "rouge"
require "better_errors/error_page_style"
module BetterErrors
@@ -158,7 +159,7 @@ def eval_and_respond(index, code)
result, prompt, prefilled_input = @repls[index].send_input(code)
{
- highlighted_input: CodeRay.scan(code, :ruby).div(wrap: nil),
+ highlighted_input: Rouge::Formatters::HTML.new.format(Rouge::Lexers::Ruby.lex(code)),
prefilled_input: prefilled_input,
prompt: prompt,
result: result
diff --git a/lib/better_errors/style/main.scss b/lib/better_errors/style/main.scss
index 41eb9fb4..db0d7d1e 100644
--- a/lib/better_errors/style/main.scss
+++ b/lib/better_errors/style/main.scss
@@ -487,12 +487,14 @@ p.no-javascript-notice {
}
.code, .be-console, .unavailable {
- background: #fff;
padding: 5px;
-
box-shadow: inset 3px 3px 3px rgba(0, 0, 0, 0.1), inset 0 0 0 1px rgba(0, 0, 0, 0.1);
}
+.code, .unavailable {
+ text-shadow: none;
+}
+
.code_linenums{
background:#f1f1f1;
padding-top:10px;
@@ -505,16 +507,35 @@ p.no-javascript-notice {
padding:0 12px;
}
+.code, .be-console .syntax-highlighted {
+ text-shadow: none;
+
+ @import "syntax_highlighting";
+}
+.code {
+ // For now, the syntax-highlighted console only supports light mode.
+ // Once the entire page has a dark theme, this should change.
+ @media (prefers-color-scheme: dark) {
+ @import "syntax_highlighting_dark";
+ }
+}
+
.code {
margin-bottom: -1px;
border-top-left-radius:2px;
padding: 10px 0;
overflow: auto;
-}
-.code pre{
- padding-left:12px;
- min-height:16px;
+ .code-wrapper {
+ // This fixes the highlight or other background of the pre not stretching the full scrollable width.
+ display: inline-block;
+ min-width: 100%;
+ }
+
+ pre {
+ padding-left:12px;
+ min-height:16px;
+ }
}
/* Source unavailable */
@@ -537,21 +558,24 @@ p.unavailable:before {
margin-bottom: -10px;
}
+$code-highlight-background: rgba(51, 136, 170, 0.15);
+$code-highlight-background-flash: adjust-color($code-highlight-background, $alpha: 0.3);
+
@-webkit-keyframes highlight {
- 0% { background: rgba(220, 30, 30, 0.3); }
- 100% { background: rgba(220, 30, 30, 0.1); }
+ 0% { background: $code-highlight-background-flash; }
+ 100% { background: $code-highlight-background; }
}
@-moz-keyframes highlight {
- 0% { background: rgba(220, 30, 30, 0.3); }
- 100% { background: rgba(220, 30, 30, 0.1); }
+ 0% { background: $code-highlight-background-flash; }
+ 100% { background: $code-highlight-background; }
}
@keyframes highlight {
- 0% { background: rgba(220, 30, 30, 0.3); }
- 100% { background: rgba(220, 30, 30, 0.1); }
+ 0% { background: $code-highlight-background-flash; }
+ 100% { background: $code-highlight-background; }
}
.code .highlight, .code_linenums .highlight {
- background: rgba(220, 30, 30, 0.1);
+ background: $code-highlight-background;
-webkit-animation: highlight 400ms linear 1;
-moz-animation: highlight 400ms linear 1;
animation: highlight 400ms linear 1;
@@ -559,6 +583,7 @@ p.unavailable:before {
/* REPL shell */
.be-console {
+ background: #fff;
padding: 0 1px 10px 1px;
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
@@ -719,5 +744,3 @@ nav.sidebar:hover::-webkit-scrollbar-thumb {
.code:hover::-webkit-scrollbar-thumb {
background: #888;
}
-
-@import "syntax_highlighting";
diff --git a/lib/better_errors/style/syntax_highlighting.scss b/lib/better_errors/style/syntax_highlighting.scss
index e24e9be8..894a91e9 100644
--- a/lib/better_errors/style/syntax_highlighting.scss
+++ b/lib/better_errors/style/syntax_highlighting.scss
@@ -1,139 +1,81 @@
-.CodeRay {
-// background-color: #FFF;
-// border: 1px solid #CCC;
-// font-family: Monaco, "Courier New", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", monospace;
- color: #000;
-// padding: 1em 0px 1em 1em;
-
- &span { white-space: pre; border: 0px; padding: 2px }
-
- &table {
- border-collapse: collapse;
- width: 100%;
- padding: 2px
- td {
- padding: 1em 0.5em;
- vertical-align: top;
- }
- }
-
- pre {
- margin: 0px;
- }
-
- .line-numbers, .no {
- background-color: #ECECEC;
- color: #AAA;
- text-align: right;
- }
-
- .line-numbers a {
- color: #AAA;
- }
-
- .line-numbers tt { font-weight: bold }
- .line-numbers .highlighted { color: red }
- .line { display: block; float: left; width: 100%; }
- span.line-numbers { padding: 0px 4px }
- .code { width: 100% }
-
- &ol {
- font-size: 10pt;
- li {
- white-space: pre
- }
- }
-
- .code pre { overflow: auto }
- .debug { color:white ! important; background:blue ! important; }
-
- .annotation { color:#007 }
- .attribute-name { color:#f08 }
- .attribute-value { color:#700 }
- .binary { color:#509; font-weight:bold }
- .comment { color:#998; font-style: italic;}
- .char { color:#04D }
- .char .content { color:#04D }
- .char .delimiter { color:#039 }
- .class { color:#458; font-weight:bold }
- .complex { color:#A08; font-weight:bold }
- .constant { color:teal; }
- .color { color:#0A0 }
- .class-variable { color:#369 }
- .decorator { color:#B0B; }
- .definition { color:#099; font-weight:bold }
- .directive { color:#088; font-weight:bold }
- .delimiter { color:black }
- .doc { color:#970 }
- .doctype { color:#34b }
- .doc-string { color:#D42; font-weight:bold }
- .escape { color:#666; font-weight:bold }
- .entity { color:#800; font-weight:bold }
- .error { color:#F00; background-color:#FAA }
- .exception { color:#C00; font-weight:bold }
- .filename { color:#099; }
- .function { color:#900; font-weight:bold }
- .global-variable { color:teal; font-weight:bold }
- .hex { color:#058; font-weight:bold }
- .integer { color:#099; }
- .include { color:#B44; font-weight:bold }
- .inline { color: black }
- .inline .inline { background: #ccc }
- .inline .inline .inline { background: #bbb }
- .inline .inline-delimiter { color: #D14; }
- .inline-delimiter { color: #D14; }
- .important { color:#f00; }
- .interpreted { color:#B2B; font-weight:bold }
- .instance-variable { color:teal }
- .label { color:#970; font-weight:bold }
- .local-variable { color:#963 }
- .octal { color:#40E; font-weight:bold }
- .operator { }
- .predefined-constant { font-weight:bold }
- .predefined { color:#369; font-weight:bold }
- .preprocessor { color:#579; }
- .pseudo-class { color:#00C; font-weight:bold }
- .predefined-type { color:#074; font-weight:bold }
- .reserved, .keyword { color:#000; font-weight:bold }
-
- .key { color: #808; }
- .key .delimiter { color: #606; }
- .key .char { color: #80f; }
- .value { color: #088; }
-
- .regexp { background-color:#fff0ff }
- .regexp .content { color:#808 }
- .regexp .delimiter { color:#404 }
- .regexp .modifier { color:#C2C }
- .regexp .function { color:#404; font-weight: bold }
-
- .string { color: #D20; }
- .string .string { }
- .string .string .string { background-color:#ffd0d0 }
- .string .content { color: #D14; }
- .string .char { color: #D14; }
- .string .delimiter { color: #D14; }
-
- .shell { color:#D14 }
- .shell .content { }
- .shell .delimiter { color:#D14 }
-
- .symbol { color:#990073 }
- .symbol .content { color:#A60 }
- .symbol .delimiter { color:#630 }
-
- .tag { color:#070 }
- .tag-special { color:#D70; font-weight:bold }
- .type { color:#339; font-weight:bold }
- .variable { color:#036 }
-
- .insert { background: #afa; }
- .delete { background: #faa; }
- .change { color: #aaf; background: #007; }
- .head { color: #f8f; background: #505 }
-
- .insert .insert { color: #080; font-weight:bold }
- .delete .delete { color: #800; font-weight:bold }
- .change .change { color: #66f; }
- .head .head { color: #f4f; }
-}
+// Thanks to https://github.com/jwarby/jekyll-pygments-themes/blob/master/pastie.css
+
+$base01: #586e75;
+$base1: #93a1a1;
+$base3: #fdf6e3;
+$orange: #cb4b16;
+$red: #dc322f;
+$blue: #268bd2;
+$cyan: #2aa198;
+$green: #859900;
+$yellow: #B58900;
+
+& { background-color: $base3; color: $base01 }
+.c { color: $base1 } /* Comment */
+.err { color: $base01 } /* Error */
+.g { color: $base01 } /* Generic */
+.k { color: $green } /* Keyword */
+.l { color: $base01 } /* Literal */
+.n { color: $base01 } /* Name */
+.o { color: $green } /* Operator */
+.x { color: $orange } /* Other */
+.p { color: $base01 } /* Punctuation */
+.cm { color: $base1 } /* Comment.Multiline */
+.cp { color: $green } /* Comment.Preproc */
+.c1 { color: $base1 } /* Comment.Single */
+.cs { color: $green } /* Comment.Special */
+.gd { color: $cyan } /* Generic.Deleted */
+.ge { color: $base01; font-style: italic } /* Generic.Emph */
+.gr { color: $red } /* Generic.Error */
+.gh { color: $orange } /* Generic.Heading */
+.gi { color: $green } /* Generic.Inserted */
+.go { color: $base01 } /* Generic.Output */
+.gp { color: $base01 } /* Generic.Prompt */
+.gs { color: $base01; font-weight: bold } /* Generic.Strong */
+.gu { color: $orange } /* Generic.Subheading */
+.gt { color: $base01 } /* Generic.Traceback */
+.kc { color: $orange } /* Keyword.Constant */
+.kd { color: $blue } /* Keyword.Declaration */
+.kn { color: $green } /* Keyword.Namespace */
+.kp { color: $green } /* Keyword.Pseudo */
+.kr { color: $blue } /* Keyword.Reserved */
+.kt { color: $red } /* Keyword.Type */
+.ld { color: $base01 } /* Literal.Date */
+.m { color: $cyan } /* Literal.Number */
+.s { color: $cyan } /* Literal.String */
+.na { color: $base01 } /* Name.Attribute */
+.nb { color: $yellow } /* Name.Builtin */
+.nc { color: $blue } /* Name.Class */
+.no { color: $orange } /* Name.Constant */
+.nd { color: $blue } /* Name.Decorator */
+.ni { color: $orange } /* Name.Entity */
+.ne { color: $orange } /* Name.Exception */
+.nf { color: $blue } /* Name.Function */
+.nl { color: $base01 } /* Name.Label */
+.nn { color: $base01 } /* Name.Namespace */
+.nx { color: $base01 } /* Name.Other */
+.py { color: $base01 } /* Name.Property */
+.nt { color: $blue } /* Name.Tag */
+.nv { color: $blue } /* Name.Variable */
+.ow { color: $green } /* Operator.Word */
+.w { color: $base01 } /* Text.Whitespace */
+.mf { color: $cyan } /* Literal.Number.Float */
+.mh { color: $cyan } /* Literal.Number.Hex */
+.mi { color: $cyan } /* Literal.Number.Integer */
+.mo { color: $cyan } /* Literal.Number.Oct */
+.sb { color: $base1 } /* Literal.String.Backtick */
+.sc { color: $cyan } /* Literal.String.Char */
+.sd { color: $base01 } /* Literal.String.Doc */
+.s2 { color: $cyan } /* Literal.String.Double */
+.se { color: $orange } /* Literal.String.Escape */
+.sh { color: $base01 } /* Literal.String.Heredoc */
+.si { color: $cyan } /* Literal.String.Interpol */
+.sx { color: $cyan } /* Literal.String.Other */
+.sr { color: $red } /* Literal.String.Regex */
+.s1 { color: $cyan } /* Literal.String.Single */
+.ss { color: $cyan } /* Literal.String.Symbol */
+.bp { color: $blue } /* Name.Builtin.Pseudo */
+.vc { color: $blue } /* Name.Variable.Class */
+.vg { color: $blue } /* Name.Variable.Global */
+.vi { color: $blue } /* Name.Variable.Instance */
+.il { color: $cyan } /* Literal.Number.Integer.Long */
diff --git a/lib/better_errors/style/syntax_highlighting_dark.scss b/lib/better_errors/style/syntax_highlighting_dark.scss
new file mode 100644
index 00000000..664d30db
--- /dev/null
+++ b/lib/better_errors/style/syntax_highlighting_dark.scss
@@ -0,0 +1,84 @@
+// Thanks to https://gist.github.com/nicolashery/5765395
+
+// Since the light theme is applied before the dark theme, the dark theme is additive.
+// So we must set the background color and font weight on everything that the light theme might have changed.
+
+$base03: #002b36;
+$base01: #586e75;
+$base1: #93a1a1;
+$orange: #cb4b16;
+$red: #dc322f;
+$blue: #268bd2;
+$cyan: #2aa198;
+$green: #859900;
+$yellow: #B58900;
+
+& { background-color: $base03; color: $base1; }
+.c { color: $base01; background-color: transparent; font-style: inherit; } /* Comment */
+.err { color: $base1; background-color: transparent; font-style: inherit; } /* Error */
+.g { color: $base1; background-color: transparent; font-style: inherit; } /* Generic */
+.k { color: $green; background-color: transparent; font-style: inherit; } /* Keyword */
+.l { color: $base1; background-color: transparent; font-style: inherit; } /* Literal */
+.n { color: $base1; background-color: transparent; font-style: inherit; } /* Name */
+.o { color: $green; background-color: transparent; font-style: inherit; } /* Operator */
+.x { color: $orange; background-color: transparent; font-style: inherit; } /* Other */
+.p { color: $base1; background-color: transparent; font-style: inherit; } /* Punctuation */
+.cm { color: $base01; background-color: transparent; font-style: inherit; } /* Comment.Multiline */
+.cp { color: $green; background-color: transparent; font-style: inherit; } /* Comment.Preproc */
+.c1 { color: $base01; background-color: transparent; font-style: inherit; } /* Comment.Single */
+.cs { color: $green; background-color: transparent; font-style: inherit; } /* Comment.Special */
+.gd { color: $cyan; background-color: transparent; font-style: inherit; } /* Generic.Deleted */
+.ge { color: $base1; background-color: transparent; font-style: italic; } /* Generic.Emph */
+.gr { color: $red; background-color: transparent; font-style: inherit; } /* Generic.Error */
+.gh { color: $orange; background-color: transparent; font-style: inherit; } /* Generic.Heading */
+.gi { color: $green; background-color: transparent; font-style: inherit; } /* Generic.Inserted */
+.go { color: $base1; background-color: transparent; font-style: inherit; } /* Generic.Output */
+.gp { color: $base1; background-color: transparent; font-style: inherit; } /* Generic.Prompt */
+.gs { color: $base1; background-color: transparent; font-weight: bold; } /* Generic.Strong */
+.gu { color: $orange; background-color: transparent; font-style: inherit; } /* Generic.Subheading */
+.gt { color: $base1; background-color: transparent; font-style: inherit; } /* Generic.Traceback */
+.kc { color: $orange; background-color: transparent; font-style: inherit; } /* Keyword.Constant */
+.kd { color: $blue; background-color: transparent; font-style: inherit; } /* Keyword.Declaration */
+.kn { color: $green; background-color: transparent; font-style: inherit; } /* Keyword.Namespace */
+.kp { color: $green; background-color: transparent; font-style: inherit; } /* Keyword.Pseudo */
+.kr { color: $blue; background-color: transparent; font-style: inherit; } /* Keyword.Reserved */
+.kt { color: $red; background-color: transparent; font-style: inherit; } /* Keyword.Type */
+.ld { color: $base1; background-color: transparent; font-style: inherit; } /* Literal.Date */
+.m { color: $cyan; background-color: transparent; font-style: inherit; } /* Literal.Number */
+.s { color: $cyan; background-color: transparent; font-style: inherit; } /* Literal.String */
+.na { color: $base1; background-color: transparent; font-style: inherit; } /* Name.Attribute */
+.nb { color: $yellow; background-color: transparent; font-style: inherit; } /* Name.Builtin */
+.nc { color: $blue; background-color: transparent; font-style: inherit; } /* Name.Class */
+.no { color: $orange; background-color: transparent; font-style: inherit; } /* Name.Constant */
+.nd { color: $blue; background-color: transparent; font-style: inherit; } /* Name.Decorator */
+.ni { color: $orange; background-color: transparent; font-style: inherit; } /* Name.Entity */
+.ne { color: $orange; background-color: transparent; font-style: inherit; } /* Name.Exception */
+.nf { color: $blue; background-color: transparent; font-style: inherit; } /* Name.Function */
+.nl { color: $base1; background-color: transparent; font-style: inherit; } /* Name.Label */
+.nn { color: $base1; background-color: transparent; font-style: inherit; } /* Name.Namespace */
+.nx { color: $base1; background-color: transparent; font-style: inherit; } /* Name.Other */
+.py { color: $base1; background-color: transparent; font-style: inherit; } /* Name.Property */
+.nt { color: $blue; background-color: transparent; font-style: inherit; } /* Name.Tag */
+.nv { color: $blue; background-color: transparent; font-style: inherit; } /* Name.Variable */
+.ow { color: $green; background-color: transparent; font-style: inherit; } /* Operator.Word */
+.w { color: $base1; background-color: transparent; font-style: inherit; } /* Text.Whitespace */
+.mf { color: $cyan; background-color: transparent; font-style: inherit; } /* Literal.Number.Float */
+.mh { color: $cyan; background-color: transparent; font-style: inherit; } /* Literal.Number.Hex */
+.mi { color: $cyan; background-color: transparent; font-style: inherit; } /* Literal.Number.Integer */
+.mo { color: $cyan; background-color: transparent; font-style: inherit; } /* Literal.Number.Oct */
+.sb { color: $base01; background-color: transparent; font-style: inherit; } /* Literal.String.Backtick */
+.sc { color: $cyan; background-color: transparent; font-style: inherit; } /* Literal.String.Char */
+.sd { color: $base1; background-color: transparent; font-style: inherit; } /* Literal.String.Doc */
+.s2 { color: $cyan; background-color: transparent; font-style: inherit; } /* Literal.String.Double */
+.se { color: $orange; background-color: transparent; font-style: inherit; } /* Literal.String.Escape */
+.sh { color: $base1; background-color: transparent; font-style: inherit; } /* Literal.String.Heredoc */
+.si { color: $cyan; background-color: transparent; font-style: inherit; } /* Literal.String.Interpol */
+.sx { color: $cyan; background-color: transparent; font-style: inherit; } /* Literal.String.Other */
+.sr { color: $red; background-color: transparent; font-style: inherit; } /* Literal.String.Regex */
+.s1 { color: $cyan; background-color: transparent; font-style: inherit; } /* Literal.String.Single */
+.ss { color: $cyan; background-color: transparent; font-style: inherit; } /* Literal.String.Symbol */
+.bp { color: $blue; background-color: transparent; font-style: inherit; } /* Name.Builtin.Pseudo */
+.vc { color: $blue; background-color: transparent; font-style: inherit; } /* Name.Variable.Class */
+.vg { color: $blue; background-color: transparent; font-style: inherit; } /* Name.Variable.Global */
+.vi { color: $blue; background-color: transparent; font-style: inherit; } /* Name.Variable.Instance */
+.il { color: $cyan; background-color: transparent; font-style: inherit; } /* Literal.Number.Integer.Long */
diff --git a/lib/better_errors/templates/main.erb b/lib/better_errors/templates/main.erb
index 7829c568..37dc30de 100644
--- a/lib/better_errors/templates/main.erb
+++ b/lib/better_errors/templates/main.erb
@@ -249,7 +249,7 @@
self.writeOutput(response.error + "\n");
}
self.writeOutput(self._prompt + " ");
- self.writeRawOutput(response.highlighted_input + "\n");
+ self.writeRawOutput("" + response.highlighted_input + "\n");
self.writeOutput(response.result);
self.setPrompt(response.prompt);
self.setInput(response.prefilled_input);
diff --git a/spec/better_errors/code_formatter/html_spec.rb b/spec/better_errors/code_formatter/html_spec.rb
new file mode 100644
index 00000000..406a9394
--- /dev/null
+++ b/spec/better_errors/code_formatter/html_spec.rb
@@ -0,0 +1,52 @@
+require "spec_helper"
+
+RSpec.describe BetterErrors::CodeFormatter::HTML do
+ let(:filename) { File.expand_path("../../support/my_source.rb", __FILE__) }
+ let(:line) { 8 }
+ let(:formatter) { described_class.new(filename, line) }
+
+ it "shows 5 lines of context above and below the line" do
+ expect(formatter.line_range).to eq(3..13)
+
+ expect(formatter.context_lines).to eq([
+ "three\n",
+ "four\n",
+ "five\n",
+ "six\n",
+ "seven\n",
+ "eight\n",
+ "nine\n",
+ "ten\n",
+ "eleven\n",
+ "twelve\n",
+ "thirteen\n"
+ ])
+ end
+
+ context 'when the line is right at the end of the file' do
+ let(:line) { 20 }
+ it "ends on the line" do
+ expect(formatter.line_range).to eq(15..20)
+ end
+ end
+
+ it "highlights the erroring line" do
+ formatter = described_class.new(filename, 8)
+ expect(formatter.output).to match(/highlight.*eight/)
+ end
+
+ it "works when the line is right on the edge" do
+ formatter = described_class.new(filename, 20)
+ expect(formatter.output).not_to eq(formatter.source_unavailable)
+ end
+
+ it "doesn't barf when the lines don't make any sense" do
+ formatter = described_class.new(filename, 999)
+ expect(formatter.output).to eq(formatter.source_unavailable)
+ end
+
+ it "doesn't barf when the file doesn't exist" do
+ formatter = described_class.new("fkdguhskd7e l", 1)
+ expect(formatter.output).to eq(formatter.source_unavailable)
+ end
+end
diff --git a/spec/better_errors/code_formatter/text_spec.rb b/spec/better_errors/code_formatter/text_spec.rb
new file mode 100644
index 00000000..a691301a
--- /dev/null
+++ b/spec/better_errors/code_formatter/text_spec.rb
@@ -0,0 +1,69 @@
+require "spec_helper"
+
+RSpec.describe BetterErrors::CodeFormatter::Text do
+ let(:filename) { File.expand_path("../../support/my_source.rb", __FILE__) }
+ let(:line) { 8 }
+ let(:formatter) { described_class.new(filename, line) }
+
+ it "shows 5 lines of context" do
+ expect(formatter.line_range).to eq(3..13)
+
+ expect(formatter.context_lines).to eq([
+ "three\n",
+ "four\n",
+ "five\n",
+ "six\n",
+ "seven\n",
+ "eight\n",
+ "nine\n",
+ "ten\n",
+ "eleven\n",
+ "twelve\n",
+ "thirteen\n"
+ ])
+ end
+
+ context 'when the line is right at the end of the file' do
+ let(:line) { 20 }
+
+ it "ends on the line" do
+ expect(formatter.line_range).to eq(15..20)
+ end
+ end
+
+ describe '#output' do
+ subject(:output) { formatter.output }
+
+ it "highlights the erroring line" do
+ expect(output).to eq <<-TEXT.gsub(/^ /, "")
+ 3 three
+ 4 four
+ 5 five
+ 6 six
+ 7 seven
+ > 8 eight
+ 9 nine
+ 10 ten
+ 11 eleven
+ 12 twelve
+ 13 thirteen
+ TEXT
+ end
+
+ context 'when the line is outside the file' do
+ let(:line) { 999 }
+
+ it "returns the 'source unavailable' message" do
+ expect(output).to eq(formatter.source_unavailable)
+ end
+ end
+
+ context 'when the the file path is not valid' do
+ let(:filename) { "fkdguhskd7e l" }
+
+ it "returns the 'source unavailable' message" do
+ expect(output).to eq(formatter.source_unavailable)
+ end
+ end
+ end
+end
diff --git a/spec/better_errors/code_formatter_spec.rb b/spec/better_errors/code_formatter_spec.rb
deleted file mode 100644
index 5b612de6..00000000
--- a/spec/better_errors/code_formatter_spec.rb
+++ /dev/null
@@ -1,92 +0,0 @@
-require "spec_helper"
-
-module BetterErrors
- describe CodeFormatter do
- let(:filename) { File.expand_path("../support/my_source.rb", __FILE__) }
-
- let(:formatter) { CodeFormatter.new(filename, 8) }
-
- it "picks an appropriate scanner" do
- expect(formatter.coderay_scanner).to eq(:ruby)
- end
-
- it "shows 5 lines of context" do
- expect(formatter.line_range).to eq(3..13)
-
- expect(formatter.context_lines).to eq([
- "three\n",
- "four\n",
- "five\n",
- "six\n",
- "seven\n",
- "eight\n",
- "nine\n",
- "ten\n",
- "eleven\n",
- "twelve\n",
- "thirteen\n"
- ])
- end
-
- it "works when the line is right on the edge" do
- formatter = CodeFormatter.new(filename, 20)
- expect(formatter.line_range).to eq(15..20)
- end
-
- describe CodeFormatter::HTML do
- it "highlights the erroring line" do
- formatter = CodeFormatter::HTML.new(filename, 8)
- expect(formatter.output).to match(/highlight.*eight/)
- end
-
- it "works when the line is right on the edge" do
- formatter = CodeFormatter::HTML.new(filename, 20)
- expect(formatter.output).not_to eq(formatter.source_unavailable)
- end
-
- it "doesn't barf when the lines don't make any sense" do
- formatter = CodeFormatter::HTML.new(filename, 999)
- expect(formatter.output).to eq(formatter.source_unavailable)
- end
-
- it "doesn't barf when the file doesn't exist" do
- formatter = CodeFormatter::HTML.new("fkdguhskd7e l", 1)
- expect(formatter.output).to eq(formatter.source_unavailable)
- end
- end
-
- describe CodeFormatter::Text do
- it "highlights the erroring line" do
- formatter = CodeFormatter::Text.new(filename, 8)
- expect(formatter.output).to eq <<-TEXT.gsub(/^ /, "")
- 3 three
- 4 four
- 5 five
- 6 six
- 7 seven
- > 8 eight
- 9 nine
- 10 ten
- 11 eleven
- 12 twelve
- 13 thirteen
- TEXT
- end
-
- it "works when the line is right on the edge" do
- formatter = CodeFormatter::Text.new(filename, 20)
- expect(formatter.output).not_to eq(formatter.source_unavailable)
- end
-
- it "doesn't barf when the lines don't make any sense" do
- formatter = CodeFormatter::Text.new(filename, 999)
- expect(formatter.output).to eq(formatter.source_unavailable)
- end
-
- it "doesn't barf when the file doesn't exist" do
- formatter = CodeFormatter::Text.new("fkdguhskd7e l", 1)
- expect(formatter.output).to eq(formatter.source_unavailable)
- end
- end
- end
-end