Skip to content

Commit

Permalink
Merge pull request #488 from BetterErrors/feature/editor-support-docker
Browse files Browse the repository at this point in the history
Improve editor support for virtual environments
  • Loading branch information
RobinDaugherty authored Nov 4, 2020
2 parents 54aa288 + 123f9b3 commit 66f2949
Show file tree
Hide file tree
Showing 6 changed files with 422 additions and 128 deletions.
47 changes: 14 additions & 33 deletions lib/better_errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,9 @@
require "better_errors/raised_exception"
require "better_errors/repl"
require "better_errors/stack_frame"
require "better_errors/editor"

module BetterErrors
POSSIBLE_EDITOR_PRESETS = [
{ symbols: [:emacs, :emacsclient], sniff: /emacs/i, url: "emacs://open?url=file://%{file}&line=%{line}" },
{ symbols: [:macvim, :mvim], sniff: /vim/i, url: proc { |file, line| "mvim://open?url=file://#{file}&line=#{line}" } },
{ symbols: [:sublime, :subl, :st], sniff: /subl/i, url: "subl://open?url=file://%{file}&line=%{line}" },
{ symbols: [:textmate, :txmt, :tm], sniff: /mate/i, url: "txmt://open?url=file://%{file}&line=%{line}" },
{ symbols: [:idea], sniff: /idea/i, url: "idea://open?file=%{file}&line=%{line}" },
{ symbols: [:rubymine], sniff: /mine/i, url: "x-mine://open?file=%{file}&line=%{line}" },
{ symbols: [:vscode, :code], sniff: /code/i, url: "vscode://file/%{file}:%{line}" },
{ symbols: [:vscodium, :codium], sniff: /codium/i, url: "vscodium://file/%{file}:%{line}" },
{ symbols: [:atom], sniff: /atom/i, url: "atom://core/open/file?filename=%{file}&line=%{line}" },
]

class << self
# The path to the root of the application. Better Errors uses this property
# to determine if a file in a backtrace should be considered an application
Expand Down Expand Up @@ -64,17 +53,18 @@ class << self
@maximum_variable_inspect_size = 100_000
@ignored_classes = ['ActionDispatch::Request', 'ActionDispatch::Response']

# Returns a proc, which when called with a filename and line number argument,
# Returns an object which responds to #url, which when called with
# a filename and line number argument,
# returns a URL to open the filename and line in the selected editor.
#
# Generates TextMate URLs by default.
#
# BetterErrors.editor["/some/file", 123]
# BetterErrors.editor.url("/some/file", 123)
# # => txmt://open?url=file:///some/file&line=123
#
# @return [Proc]
def self.editor
@editor
@editor ||= default_editor
end

# Configures how Better Errors generates open-in-editor URLs.
Expand Down Expand Up @@ -115,20 +105,15 @@ def self.editor
# @param [Proc] proc
#
def self.editor=(editor)
POSSIBLE_EDITOR_PRESETS.each do |config|
if config[:symbols].include?(editor)
return self.editor = config[:url]
end
end

if editor.is_a? String
self.editor = proc { |file, line| editor % { file: URI.encode_www_form_component(file), line: line } }
if editor.is_a? Symbol
@editor = Editor.for_symbol(editor)
raise(ArgumentError, "Symbol #{editor} is not a symbol in the list of supported errors.") unless editor
elsif editor.is_a? String
@editor = Editor.for_formatting_string(editor)
elsif editor.respond_to? :call
@editor = Editor.for_proc(editor)
else
if editor.respond_to? :call
@editor = editor
else
raise TypeError, "Expected editor to be a valid editor key, a format string or a callable."
end
raise ArgumentError, "Expected editor to be a valid editor key, a format string or a callable."
end
end

Expand All @@ -145,12 +130,8 @@ def self.use_pry!
#
# @return [Symbol]
def self.default_editor
POSSIBLE_EDITOR_PRESETS.detect(-> { {} }) { |config|
ENV["EDITOR"] =~ config[:sniff]
}[:url] || :textmate
Editor.default_editor
end

BetterErrors.editor = default_editor
end

begin
Expand Down
99 changes: 99 additions & 0 deletions lib/better_errors/editor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
require "uri"

module BetterErrors
class Editor
KNOWN_EDITORS = [
{ symbols: [:atom], sniff: /atom/i, url: "atom://core/open/file?filename=%{file}&line=%{line}" },
{ symbols: [:emacs, :emacsclient], sniff: /emacs/i, url: "emacs://open?url=file://%{file}&line=%{line}" },
{ symbols: [:idea], sniff: /idea/i, url: "idea://open?file=%{file}&line=%{line}" },
{ symbols: [:macvim, :mvim], sniff: /vim/i, url: "mvim://open?url=file://%{file_unencoded}&line=%{line}" },
{ symbols: [:rubymine], sniff: /mine/i, url: "x-mine://open?file=%{file}&line=%{line}" },
{ symbols: [:sublime, :subl, :st], sniff: /subl/i, url: "subl://open?url=file://%{file}&line=%{line}" },
{ symbols: [:textmate, :txmt, :tm], sniff: /mate/i, url: "txmt://open?url=file://%{file}&line=%{line}" },
{ symbols: [:vscode, :code], sniff: /code/i, url: "vscode://file/%{file}:%{line}" },
{ symbols: [:vscodium, :codium], sniff: /codium/i, url: "vscodium://file/%{file}:%{line}" },
]

def self.for_formatting_string(formatting_string)
new proc { |file, line|
formatting_string % { file: URI.encode_www_form_component(file), file_unencoded: file, line: line }
}
end

def self.for_proc(url_proc)
new url_proc
end

# Automatically sniffs a default editor preset based on
# environment variables.
#
# @return [Symbol]
def self.default_editor
editor_from_environment_formatting_string ||
editor_from_environment_editor ||
editor_from_symbol(:textmate)
end

def self.editor_from_environment_editor
if ENV["BETTER_ERRORS_EDITOR"]
editor = editor_from_command(ENV["BETTER_ERRORS_EDITOR"])
return editor if editor
puts "BETTER_ERRORS_EDITOR environment variable is not recognized as a supported Better Errors editor."
end
if ENV["EDITOR"]
editor = editor_from_command(ENV["EDITOR"])
return editor if editor
puts "EDITOR environment variable is not recognized as a supported Better Errors editor. Using TextMate by default."
else
puts "Since there is no EDITOR or BETTER_ERRORS_EDITOR environment variable, using Textmate by default."
end
end

def self.editor_from_command(editor_command)
env_preset = KNOWN_EDITORS.find { |preset| editor_command =~ preset[:sniff] }
for_formatting_string(env_preset[:url]) if env_preset
end

def self.editor_from_environment_formatting_string
return unless ENV['BETTER_ERRORS_EDITOR_URL']

for_formatting_string(ENV['BETTER_ERRORS_EDITOR_URL'])
end

def self.editor_from_symbol(symbol)
KNOWN_EDITORS.each do |preset|
return for_formatting_string(preset[:url]) if preset[:symbols].include?(symbol)
end
end

def initialize(url_proc)
@url_proc = url_proc
end

def url(raw_path, line)
if virtual_path && raw_path.start_with?(virtual_path)
if host_path
file = raw_path.sub(%r{\A#{virtual_path}}, host_path)
else
file = raw_path.sub(%r{\A#{virtual_path}/}, '')
end
else
file = raw_path
end

url_proc.call(file, line)
end

private

attr_reader :url_proc

def virtual_path
@virtual_path ||= ENV['BETTER_ERRORS_VIRTUAL_PATH']
end

def host_path
@host_path ||= ENV['BETTER_ERRORS_HOST_PATH']
end
end
end
2 changes: 1 addition & 1 deletion lib/better_errors/error_page.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def first_frame
private

def editor_url(frame)
BetterErrors.editor[frame.filename, frame.line]
BetterErrors.editor.url(frame.filename, frame.line)
end

def rack_session
Expand Down
Loading

0 comments on commit 66f2949

Please sign in to comment.