Skip to content

Commit 66f2949

Browse files
Merge pull request #488 from BetterErrors/feature/editor-support-docker
Improve editor support for virtual environments
2 parents 54aa288 + 123f9b3 commit 66f2949

File tree

6 files changed

+422
-128
lines changed

6 files changed

+422
-128
lines changed

lib/better_errors.rb

Lines changed: 14 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,15 @@ 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.is_a? Symbol
109+
@editor = Editor.for_symbol(editor)
110+
raise(ArgumentError, "Symbol #{editor} is not a symbol in the list of supported errors.") unless editor
111+
elsif editor.is_a? String
112+
@editor = Editor.for_formatting_string(editor)
113+
elsif editor.respond_to? :call
114+
@editor = Editor.for_proc(editor)
126115
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
116+
raise ArgumentError, "Expected editor to be a valid editor key, a format string or a callable."
132117
end
133118
end
134119

@@ -145,12 +130,8 @@ def self.use_pry!
145130
#
146131
# @return [Symbol]
147132
def self.default_editor
148-
POSSIBLE_EDITOR_PRESETS.detect(-> { {} }) { |config|
149-
ENV["EDITOR"] =~ config[:sniff]
150-
}[:url] || :textmate
133+
Editor.default_editor
151134
end
152-
153-
BetterErrors.editor = default_editor
154135
end
155136

156137
begin

lib/better_errors/editor.rb

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
require "uri"
2+
3+
module BetterErrors
4+
class 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+
def self.for_formatting_string(formatting_string)
18+
new proc { |file, line|
19+
formatting_string % { file: URI.encode_www_form_component(file), file_unencoded: file, line: line }
20+
}
21+
end
22+
23+
def self.for_proc(url_proc)
24+
new url_proc
25+
end
26+
27+
# Automatically sniffs a default editor preset based on
28+
# environment variables.
29+
#
30+
# @return [Symbol]
31+
def self.default_editor
32+
editor_from_environment_formatting_string ||
33+
editor_from_environment_editor ||
34+
editor_from_symbol(:textmate)
35+
end
36+
37+
def self.editor_from_environment_editor
38+
if ENV["BETTER_ERRORS_EDITOR"]
39+
editor = editor_from_command(ENV["BETTER_ERRORS_EDITOR"])
40+
return editor if editor
41+
puts "BETTER_ERRORS_EDITOR environment variable is not recognized as a supported Better Errors editor."
42+
end
43+
if ENV["EDITOR"]
44+
editor = editor_from_command(ENV["EDITOR"])
45+
return editor if editor
46+
puts "EDITOR environment variable is not recognized as a supported Better Errors editor. Using TextMate by default."
47+
else
48+
puts "Since there is no EDITOR or BETTER_ERRORS_EDITOR environment variable, using Textmate by default."
49+
end
50+
end
51+
52+
def self.editor_from_command(editor_command)
53+
env_preset = KNOWN_EDITORS.find { |preset| editor_command =~ preset[:sniff] }
54+
for_formatting_string(env_preset[:url]) if env_preset
55+
end
56+
57+
def self.editor_from_environment_formatting_string
58+
return unless ENV['BETTER_ERRORS_EDITOR_URL']
59+
60+
for_formatting_string(ENV['BETTER_ERRORS_EDITOR_URL'])
61+
end
62+
63+
def self.editor_from_symbol(symbol)
64+
KNOWN_EDITORS.each do |preset|
65+
return for_formatting_string(preset[:url]) if preset[:symbols].include?(symbol)
66+
end
67+
end
68+
69+
def initialize(url_proc)
70+
@url_proc = url_proc
71+
end
72+
73+
def url(raw_path, line)
74+
if virtual_path && raw_path.start_with?(virtual_path)
75+
if host_path
76+
file = raw_path.sub(%r{\A#{virtual_path}}, host_path)
77+
else
78+
file = raw_path.sub(%r{\A#{virtual_path}/}, '')
79+
end
80+
else
81+
file = raw_path
82+
end
83+
84+
url_proc.call(file, line)
85+
end
86+
87+
private
88+
89+
attr_reader :url_proc
90+
91+
def virtual_path
92+
@virtual_path ||= ENV['BETTER_ERRORS_VIRTUAL_PATH']
93+
end
94+
95+
def host_path
96+
@host_path ||= ENV['BETTER_ERRORS_HOST_PATH']
97+
end
98+
end
99+
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

0 commit comments

Comments
 (0)