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

201 lines
6.6 KiB
Ruby
Raw Normal View History

class Pry
class Command::Edit < Pry::ClassCommand
require 'pry/commands/edit/exception_patcher'
require 'pry/commands/edit/file_and_line_locator'
match 'edit'
group 'Editing'
description 'Invoke the default editor on a file.'
banner <<-'BANNER'
2012-12-30 23:48:48 -05:00
Usage: edit [--no-reload|--reload|--patch] [--line LINE] [--temp|--ex|FILE[:LINE]|OBJECT|--in N]
2013-01-17 22:32:03 -05:00
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
2013-01-17 22:32:03 -05:00
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
2014-04-29 03:27:16 -04:00
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
2012-12-30 23:48:48 -05:00
end
Pry.history.push(content)
2012-12-30 23:48:48 -05:00
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
2012-12-30 23:48:48 -05:00
def apply_runtime_patch
if patch_exception?
ExceptionPatcher.new(_pry_, state, file_and_line_for_current_exception).perform_patch
2012-12-30 23:48:48 -05:00
else
if code_object.is_a?(Pry::Method)
2014-04-29 03:27:16 -04:00
code_object.redefine Pry::Editor.new(_pry_).edit_tempfile_with_content(code_object.source)
2012-12-30 23:48:48 -05:00
else
raise NotImplementedError, "Cannot yet patch #{code_object} objects!"
end
end
end
def ensure_file_name_is_valid(file_name)
raise CommandError, "Cannot find a valid file for #{filename_argument}" if !file_name
raise CommandError, "#{file_name} is not a valid file name, cannot edit!" if not_a_real_file?(file_name)
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)
2012-12-30 23:48:48 -05:00
2014-04-29 03:27:16 -04:00
Pry::Editor.new(_pry_).invoke_editor(file_name, line, reload?(file_name))
set_file_and_dir_locals(file_name)
2012-12-30 23:48:48 -05:00
if reload?(file_name)
silence_warnings do
2013-03-23 02:26:52 -04:00
load file_name
2012-12-30 23:48:48 -05:00
end
end
end
def filename_argument
args.join(' ')
2012-12-30 23:48:48 -05:00
end
def code_object
@code_object ||= !probably_a_file?(filename_argument) &&
Pry::CodeObject.lookup(filename_argument, _pry_)
end
def pry_method?(code_object)
2012-12-30 23:48:48 -05:00
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
2017-06-13 17:16:09 -04:00
when Integer
_pry_.input_ring[opts[:i]] || ""
else
2012-08-18 23:58:48 -04:00
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
case
when opts.present?(:temp)
""
when opts.present?(:in)
input_expression
when eval_string.strip != ""
eval_string
else
ring: rewrite the class to improve API Currently, the Ring class is written with help of Hash as backend store. According to the comments, the implementation should behave like a circular buffer, however it doesn't. Upon reaching maximum capacity Ring doesn't replace old elements but keeps writing to new cells, deleting old cells, so that the hash contains `nil` entries. The new implementation is based on Array and seems to be closer to the actual Ring. Older elemens get overwritten with newer ones. This class also includes Enumerable, however none of our APIs take advantage of it, so it seems like an overkill. There was also a problem with with this API because of the above-mentioned nils. For example, if the ring exceeds its maximum size, then callin `Enumerable#first` on it returns `nil`. The new implementation deals with this with removal of Enumerable. The `#[]` syntax is preserved, and now `ring[0]` returns an actual element instead of `nil`. In case users need the Enumerable functionality, they can call `Ring#to_a` to build the array, which supports the wanted methods. As for the speed, the new implementation is: * slower overall because it's thread-safe * faster without mutexes for `#<<` * slower without mutexes for `#[]` Benchmark for old implementation: ``` Warming up -------------------------------------- Ring#<< 223.451k i/100ms Ring#[] 2.837k i/100ms Calculating ------------------------------------- Ring#<< 223.157B (± 3.4%) i/s - 778.097B Ring#[] 82.485M (± 9.4%) i/s - 402.602M in 4.957792s ``` Benchmark for this implementation: ``` Warming up -------------------------------------- Ring#<< 211.587k i/100ms Ring#[] 1.974k i/100ms Calculating ------------------------------------- Ring#<< 211.385B (± 2.8%) i/s - 698.439B Ring#[] 40.292M (±17.0%) i/s - 190.069M in 4.971195s ``` The benchmark: ```rb require './lib/pry' require 'benchmark/ips' Benchmark.ips do |x| empty_ring = Pry::Ring.new(100) populated_ring = Pry::Ring.new(100) 150.times { |i| populated_ring << i } x.report("Ring#<<") do |times| empty_ring << times end x.report("Ring#[]") do |times| populated_ring[0] populated_ring[1] populated_ring[2] populated_ring[-1] populated_ring[-2] populated_ring[-3] populated_ring[1..2] populated_ring[-2..-1] populated_ring[-2..3] populated_ring[0..-1] populated_ring[2..-1] populated_ring[-1..10] populated_ring[-1..0] populated_ring[-1..1] end end ```
2018-10-20 16:58:56 -04:00
_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 =~ /\/|\\/
end
end
Pry::Commands.add_command(Pry::Command::Edit)
end