Skip to content

Commit 4f95f3b

Browse files
Refactor to Editor module and call pattern
Adding support for BETTER_ERRORS_EDITOR env var.
1 parent 54aa288 commit 4f95f3b

File tree

5 files changed

+114
-49
lines changed

5 files changed

+114
-49
lines changed

lib/better_errors.rb

Lines changed: 16 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,9 @@
1111
require "better_errors/raised_exception"
1212
require "better_errors/repl"
1313
require "better_errors/stack_frame"
14+
require "better_errors/editor"
1415

1516
module BetterErrors
16-
POSSIBLE_EDITOR_PRESETS = [
17-
{ symbols: [:emacs, :emacsclient], sniff: /emacs/i, url: "emacs://open?url=file://%{file}&line=%{line}" },
18-
{ symbols: [:macvim, :mvim], sniff: /vim/i, url: proc { |file, line| "mvim://open?url=file://#{file}&line=#{line}" } },
19-
{ symbols: [:sublime, :subl, :st], sniff: /subl/i, url: "subl://open?url=file://%{file}&line=%{line}" },
20-
{ symbols: [:textmate, :txmt, :tm], sniff: /mate/i, url: "txmt://open?url=file://%{file}&line=%{line}" },
21-
{ symbols: [:idea], sniff: /idea/i, url: "idea://open?file=%{file}&line=%{line}" },
22-
{ symbols: [:rubymine], sniff: /mine/i, url: "x-mine://open?file=%{file}&line=%{line}" },
23-
{ symbols: [:vscode, :code], sniff: /code/i, url: "vscode://file/%{file}:%{line}" },
24-
{ symbols: [:vscodium, :codium], sniff: /codium/i, url: "vscodium://file/%{file}:%{line}" },
25-
{ symbols: [:atom], sniff: /atom/i, url: "atom://core/open/file?filename=%{file}&line=%{line}" },
26-
]
27-
2817
class << self
2918
# The path to the root of the application. Better Errors uses this property
3019
# to determine if a file in a backtrace should be considered an application
@@ -64,17 +53,18 @@ class << self
6453
@maximum_variable_inspect_size = 100_000
6554
@ignored_classes = ['ActionDispatch::Request', 'ActionDispatch::Response']
6655

67-
# Returns a proc, which when called with a filename and line number argument,
56+
# Returns an object which responds to #url, which when called with
57+
# a filename and line number argument,
6858
# returns a URL to open the filename and line in the selected editor.
6959
#
7060
# Generates TextMate URLs by default.
7161
#
72-
# BetterErrors.editor["/some/file", 123]
62+
# BetterErrors.editor.url("/some/file", 123)
7363
# # => txmt://open?url=file:///some/file&line=123
7464
#
7565
# @return [Proc]
7666
def self.editor
77-
@editor
67+
@editor ||= default_editor
7868
end
7969

8070
# Configures how Better Errors generates open-in-editor URLs.
@@ -115,20 +105,17 @@ def self.editor
115105
# @param [Proc] proc
116106
#
117107
def self.editor=(editor)
118-
POSSIBLE_EDITOR_PRESETS.each do |config|
119-
if config[:symbols].include?(editor)
120-
return self.editor = config[:url]
121-
end
122-
end
123-
124-
if editor.is_a? String
125-
self.editor = proc { |file, line| editor % { file: URI.encode_www_form_component(file), line: line } }
108+
if editor.respond_to? :url
109+
@editor = editor
110+
elsif editor.is_a? Symbol
111+
@editor = Editor.for_symbol(editor)
112+
raise(ArgumentError, "Symbol #{editor} is not a symbol in the list of supported errors.") unless editor
113+
elsif editor.is_a? String
114+
@editor = Editor.for_formatting_string(editor)
115+
elsif editor.respond_to? :call
116+
@editor = Editor.for_proc(editor)
126117
else
127-
if editor.respond_to? :call
128-
@editor = editor
129-
else
130-
raise TypeError, "Expected editor to be a valid editor key, a format string or a callable."
131-
end
118+
raise ArgumentError, "Expected editor to be a valid editor key, a format string or a callable."
132119
end
133120
end
134121

@@ -145,12 +132,8 @@ def self.use_pry!
145132
#
146133
# @return [Symbol]
147134
def self.default_editor
148-
POSSIBLE_EDITOR_PRESETS.detect(-> { {} }) { |config|
149-
ENV["EDITOR"] =~ config[:sniff]
150-
}[:url] || :textmate
135+
Editor.default_editor
151136
end
152-
153-
BetterErrors.editor = default_editor
154137
end
155138

156139
begin

lib/better_errors/editor.rb

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
require "uri"
2+
3+
module BetterErrors
4+
module Editor
5+
KNOWN_EDITORS = [
6+
{ symbols: [:atom], sniff: /atom/i, url: "atom://core/open/file?filename=%{file}&line=%{line}" },
7+
{ symbols: [:emacs, :emacsclient], sniff: /emacs/i, url: "emacs://open?url=file://%{file}&line=%{line}" },
8+
{ symbols: [:idea], sniff: /idea/i, url: "idea://open?file=%{file}&line=%{line}" },
9+
{ symbols: [:macvim, :mvim], sniff: /vim/i, url: "mvim://open?url=file://%{file_unencoded}&line=%{line}" },
10+
{ symbols: [:rubymine], sniff: /mine/i, url: "x-mine://open?file=%{file}&line=%{line}" },
11+
{ symbols: [:sublime, :subl, :st], sniff: /subl/i, url: "subl://open?url=file://%{file}&line=%{line}" },
12+
{ symbols: [:textmate, :txmt, :tm], sniff: /mate/i, url: "txmt://open?url=file://%{file}&line=%{line}" },
13+
{ symbols: [:vscode, :code], sniff: /code/i, url: "vscode://file/%{file}:%{line}" },
14+
{ symbols: [:vscodium, :codium], sniff: /codium/i, url: "vscodium://file/%{file}:%{line}" },
15+
]
16+
17+
class UsingFormattingString
18+
def initialize(url_formatting_string)
19+
@url_formatting_string = url_formatting_string
20+
end
21+
22+
def url(file, line)
23+
url_formatting_string % { file: URI.encode_www_form_component(file), file_unencoded: file, line: line }
24+
end
25+
26+
private
27+
28+
attr_reader :url_formatting_string
29+
end
30+
31+
class UsingProc
32+
def initialize(url_proc)
33+
@url_proc = url_proc
34+
end
35+
36+
def url(file, line)
37+
url_proc.call(file, line)
38+
end
39+
40+
private
41+
42+
attr_reader :url_proc
43+
end
44+
45+
def self.for_formatting_string(formatting_string)
46+
UsingFormattingString.new(formatting_string)
47+
end
48+
49+
def self.for_proc(url_proc)
50+
UsingProc.new(url_proc)
51+
end
52+
53+
def self.for_symbol(symbol)
54+
KNOWN_EDITORS.each do |preset|
55+
return for_formatting_string(preset[:url]) if preset[:symbols].include?(symbol)
56+
end
57+
end
58+
59+
# Automatically sniffs a default editor preset based on the EDITOR
60+
# environment variable.
61+
#
62+
# @return [Symbol]
63+
def self.default_editor
64+
editor_command = ENV["EDITOR"] || ENV["BETTER_ERRORS_EDITOR"]
65+
if editor_command
66+
editor = editor_from_command(editor_command)
67+
return editor if editor
68+
69+
puts "Since EDITOR or BETTER_ERRORS_EDITOR environment variable are not recognized, using Textmate by default."
70+
else
71+
puts "Since there is no EDITOR or BETTER_ERRORS_EDITOR environment variable, using Textmate by default."
72+
end
73+
for_symbol(:textmate)
74+
end
75+
76+
def self.editor_from_command(editor_command)
77+
env_preset = KNOWN_EDITORS.find { |preset| editor_command =~ preset[:sniff] }
78+
for_formatting_string(env_preset[:url]) if env_preset
79+
end
80+
end
81+
end

lib/better_errors/error_page.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def first_frame
9494
private
9595

9696
def editor_url(frame)
97-
BetterErrors.editor[frame.filename, frame.line]
97+
BetterErrors.editor.url(frame.filename, frame.line)
9898
end
9999

100100
def rack_session

spec/better_errors_spec.rb

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,77 +3,77 @@
33
describe BetterErrors do
44
context ".editor" do
55
it "defaults to textmate" do
6-
expect(subject.editor["foo.rb", 123]).to eq("txmt://open?url=file://foo.rb&line=123")
6+
expect(subject.editor.url("foo.rb", 123)).to eq("txmt://open?url=file://foo.rb&line=123")
77
end
88

99
it "url escapes the filename" do
10-
expect(subject.editor["&.rb", 0]).to eq("txmt://open?url=file://%26.rb&line=0")
10+
expect(subject.editor.url("&.rb", 0)).to eq("txmt://open?url=file://%26.rb&line=0")
1111
end
1212

1313
[:emacs, :emacsclient].each do |editor|
1414
it "uses emacs:// scheme when set to #{editor.inspect}" do
1515
subject.editor = editor
16-
expect(subject.editor[]).to start_with "emacs://"
16+
expect(subject.editor.url("file", 42)).to start_with "emacs://"
1717
end
1818
end
1919

2020
[:macvim, :mvim].each do |editor|
2121
it "uses mvim:// scheme when set to #{editor.inspect}" do
2222
subject.editor = editor
23-
expect(subject.editor[]).to start_with "mvim://"
23+
expect(subject.editor.url("file", 42)).to start_with "mvim://"
2424
end
2525
end
2626

2727
[:sublime, :subl, :st].each do |editor|
2828
it "uses subl:// scheme when set to #{editor.inspect}" do
2929
subject.editor = editor
30-
expect(subject.editor[]).to start_with "subl://"
30+
expect(subject.editor.url("file", 42)).to start_with "subl://"
3131
end
3232
end
3333

3434
[:textmate, :txmt, :tm].each do |editor|
3535
it "uses txmt:// scheme when set to #{editor.inspect}" do
3636
subject.editor = editor
37-
expect(subject.editor[]).to start_with "txmt://"
37+
expect(subject.editor.url("file", 42)).to start_with "txmt://"
3838
end
3939
end
4040

4141
[:atom].each do |editor|
4242
it "uses atom:// scheme when set to #{editor.inspect}" do
4343
subject.editor = editor
44-
expect(subject.editor[]).to start_with "atom://"
44+
expect(subject.editor.url("file", 42)).to start_with "atom://"
4545
end
4646
end
4747

4848
["emacsclient", "/usr/local/bin/emacsclient"].each do |editor|
4949
it "uses emacs:// scheme when EDITOR=#{editor}" do
5050
ENV["EDITOR"] = editor
5151
subject.editor = subject.default_editor
52-
expect(subject.editor[]).to start_with "emacs://"
52+
expect(subject.editor.url("file", 42)).to start_with "emacs://"
5353
end
5454
end
5555

5656
["mvim -f", "/usr/local/bin/mvim -f"].each do |editor|
5757
it "uses mvim:// scheme when EDITOR=#{editor}" do
5858
ENV["EDITOR"] = editor
5959
subject.editor = subject.default_editor
60-
expect(subject.editor[]).to start_with "mvim://"
60+
expect(subject.editor.url("file", 42)).to start_with "mvim://"
6161
end
6262
end
6363

6464
["subl -w", "/Applications/Sublime Text 2.app/Contents/SharedSupport/bin/subl"].each do |editor|
6565
it "uses subl:// scheme when EDITOR=#{editor}" do
6666
ENV["EDITOR"] = editor
6767
subject.editor = subject.default_editor
68-
expect(subject.editor[]).to start_with "subl://"
68+
expect(subject.editor.url("file", 42)).to start_with "subl://"
6969
end
7070
end
7171

7272
["mate -w", "/usr/bin/mate -w"].each do |editor|
7373
it "uses txmt:// scheme when EDITOR=#{editor}" do
7474
ENV["EDITOR"] = editor
7575
subject.editor = subject.default_editor
76-
expect(subject.editor[]).to start_with "txmt://"
76+
expect(subject.editor.url("file", 42)).to start_with "txmt://"
7777
end
7878
end
7979

@@ -82,31 +82,31 @@
8282
it "uses atom:// scheme when EDITOR=#{editor}" do
8383
ENV["EDITOR"] = editor
8484
subject.editor = subject.default_editor
85-
expect(subject.editor[]).to start_with "atom://"
85+
expect(subject.editor.url("file", 42)).to start_with "atom://"
8686
end
8787
end
8888

8989
["mine"].each do |editor|
9090
it "uses x-mine:// scheme when EDITOR=#{editor}" do
9191
ENV["EDITOR"] = editor
9292
subject.editor = subject.default_editor
93-
expect(subject.editor[]).to start_with "x-mine://"
93+
expect(subject.editor.url("file", 42)).to start_with "x-mine://"
9494
end
9595
end
9696

9797
["idea"].each do |editor|
9898
it "uses idea:// scheme when EDITOR=#{editor}" do
9999
ENV["EDITOR"] = editor
100100
subject.editor = subject.default_editor
101-
expect(subject.editor[]).to start_with "idea://"
101+
expect(subject.editor.url("file", 42)).to start_with "idea://"
102102
end
103103
end
104104

105105
["vscode", "code"].each do |editor|
106106
it "uses vscode:// scheme when EDITOR=#{editor}" do
107107
ENV["EDITOR"] = editor
108108
subject.editor = subject.default_editor
109-
expect(subject.editor[]).to start_with "vscode://"
109+
expect(subject.editor.url("file", 42)).to start_with "vscode://"
110110
end
111111
end
112112
end

spec/spec_helper.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
$: << File.expand_path("../../lib", __FILE__)
22

33
ENV["EDITOR"] = nil
4+
ENV["BETTER_ERRORS"] = nil
45

56
require 'simplecov'
67
require 'simplecov-lcov'

0 commit comments

Comments
 (0)