1
0
Fork 0
mirror of https://github.com/pry/pry.git synced 2022-11-09 12:35:05 -05:00
pry--pry/lib/pry/commands/edit.rb
Kyrylo Silin edaa1383f2 Require files from pry.rb; deps from each file that uses them
This change brings some order to how we require files. Previously, we required
app files from everywhere, including pry.rb. Now we require app files only from
pry.rb.

External and stdlib dependencies are required at places where they're used, not
globally.
2019-03-10 13:20:03 +02:00

227 lines
7.1 KiB
Ruby

class Pry
class Command
class Edit < Pry::ClassCommand
match 'edit'
group 'Editing'
description 'Invoke the default editor on a file.'
banner <<-'BANNER'
Usage: edit [--no-reload|--reload|--patch] [--line LINE] [--temp|--ex|FILE[:LINE]|OBJECT|--in N]
Open a text editor. When no FILE is given, edits the pry input buffer.
When a method/module/command is given, the code is opened in an editor.
Ensure `Pry.config.editor` or `_pry_.config.editor` is set to your editor of choice.
edit sample.rb edit -p MyClass#my_method
edit sample.rb --line 105 edit MyClass
edit MyClass#my_method edit --ex
edit --method edit --ex -p
https://github.com/pry/pry/wiki/Editor-integration#wiki-Edit_command
BANNER
def options(opt)
opt.on :e, :ex, "Open the file that raised the most recent exception " \
"(_ex_.file)",
optional_argument: true, as: Integer
opt.on :i, :in, "Open a temporary file containing the Nth input " \
"expression. N may be a range",
optional_argument: true, as: Range, default: -1..-1
opt.on :t, :temp, "Open an empty temporary file"
opt.on :l, :line, "Jump to this line in the opened file",
argument: true, as: Integer
opt.on :n, :"no-reload", "Don't automatically reload the edited file"
opt.on :c, :current, "Open the current __FILE__ and at __LINE__ (as " \
"returned by `whereami`)"
opt.on :r, :reload, "Reload the edited code immediately (default for " \
"ruby files)"
opt.on :p, :patch, "Instead of editing the object's file, try to edit " \
"in a tempfile and apply as a monkey patch"
opt.on :m, :method, "Explicitly edit the _current_ method (when " \
"inside a method context)."
end
def process
if bad_option_combination?
raise CommandError, "Only one of --ex, --temp, --in, --method and " \
"FILE may be specified."
end
if repl_edit?
# code defined in pry, eval'd within pry.
repl_edit
elsif runtime_patch?
# patch code without persisting changes, implies future changes are patches
apply_runtime_patch
else
# code stored in actual files, eval'd at top-level
file_edit
end
end
def repl_edit?
!opts.present?(:ex) && !opts.present?(:current) && !opts.present?(:method) &&
filename_argument.empty?
end
def repl_edit
content = Pry::Editor.new(_pry_).edit_tempfile_with_content(
initial_temp_file_content,
initial_temp_file_content.lines.count
)
silence_warnings do
eval_string.replace content
end
Pry.history.push(content)
end
def file_based_exception?
opts.present?(:ex) && !opts.present?(:patch)
end
def runtime_patch?
!file_based_exception? &&
(opts.present?(:patch) ||
previously_patched?(code_object) ||
pry_method?(code_object))
end
def apply_runtime_patch
if patch_exception?
ExceptionPatcher.new(
_pry_, state, file_and_line_for_current_exception
).perform_patch
else
if code_object.is_a?(Pry::Method)
code_object.redefine(
Pry::Editor.new(_pry_).edit_tempfile_with_content(
code_object.source
)
)
else
raise NotImplementedError, "Cannot yet patch #{code_object} objects!"
end
end
end
def ensure_file_name_is_valid(file_name)
unless file_name
raise CommandError, "Cannot find a valid file for #{filename_argument}"
end
if not_a_real_file?(file_name)
raise CommandError, "#{file_name} is not a valid file name, cannot edit!"
end
end
def file_and_line_for_current_exception
FileAndLineLocator.from_exception(_pry_.last_exception, opts[:ex].to_i)
end
def file_and_line
file_name, line =
if opts.present?(:current)
FileAndLineLocator.from_binding(target)
elsif opts.present?(:ex)
file_and_line_for_current_exception
elsif code_object
FileAndLineLocator.from_code_object(code_object, filename_argument)
else
# when file and line are passed as a single arg, e.g my_file.rb:30
FileAndLineLocator.from_filename_argument(filename_argument)
end
[file_name, opts.present?(:line) ? opts[:l].to_i : line]
end
def file_edit
file_name, line = file_and_line
ensure_file_name_is_valid(file_name)
Pry::Editor.new(_pry_).invoke_editor(file_name, line, reload?(file_name))
set_file_and_dir_locals(file_name)
if reload?(file_name)
silence_warnings do
load file_name
end
end
end
def filename_argument
args.join(' ')
end
def code_object
@code_object ||=
!probably_a_file?(filename_argument) &&
Pry::CodeObject.lookup(filename_argument, _pry_)
end
def pry_method?(code_object)
code_object.is_a?(Pry::Method) &&
code_object.pry_method?
end
def previously_patched?(code_object)
code_object.is_a?(Pry::Method) &&
Pry::Method::Patcher.code_for(code_object.source_location.first)
end
def patch_exception?
opts.present?(:ex) && opts.present?(:patch)
end
def bad_option_combination?
[
opts.present?(:ex), opts.present?(:temp),
opts.present?(:in), opts.present?(:method),
!filename_argument.empty?
].count(true) > 1
end
def input_expression
case opts[:i]
when Range
(_pry_.input_ring[opts[:i]] || []).join
when Integer
_pry_.input_ring[opts[:i]] || ""
else
raise Pry::CommandError, "Not a valid range: #{opts[:i]}"
end
end
def reloadable?
opts.present?(:reload) || opts.present?(:ex)
end
def never_reload?
opts.present?(:'no-reload') || _pry_.config.disable_auto_reload
end
def reload?(file_name = "")
(reloadable? || file_name.end_with?(".rb")) && !never_reload?
end
def initial_temp_file_content
if opts.present?(:temp)
""
elsif opts.present?(:in)
input_expression
elsif eval_string.strip != ""
eval_string
else
_pry_.input_ring.to_a.reverse_each.find { |x| x && x.strip != "" } || ""
end
end
def probably_a_file?(str)
[".rb", ".c", ".py", ".yml", ".gemspec"].include?(File.extname(str)) ||
str =~ %r{/|\\}
end
end
Pry::Commands.add_command(Pry::Command::Edit)
end
end