2012-08-11 20:22:29 -04:00
|
|
|
class Pry
|
2012-12-25 16:35:17 -05:00
|
|
|
class Command::Edit < Pry::ClassCommand
|
2012-12-31 13:13:25 -05:00
|
|
|
require 'pry/commands/edit/exception_patcher'
|
2013-01-09 13:42:00 -05:00
|
|
|
require 'pry/commands/edit/file_and_line_locator'
|
2012-12-30 23:15:25 -05:00
|
|
|
|
2012-12-25 16:35:17 -05:00
|
|
|
match 'edit'
|
2012-08-11 21:26:59 -04:00
|
|
|
group 'Editing'
|
2012-12-25 16:35:17 -05:00
|
|
|
description 'Invoke the default editor on a file.'
|
2012-08-11 20:22:29 -04:00
|
|
|
|
2013-01-09 15:23:19 -05:00
|
|
|
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]
|
2012-08-11 20:22:29 -04:00
|
|
|
|
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.
|
2014-02-06 20:01:39 -05:00
|
|
|
Ensure `Pry.config.editor` or `_pry_.config.editor` is set to your editor of choice.
|
2012-08-11 20:22:29 -04:00
|
|
|
|
2013-01-12 12:34:13 -05:00
|
|
|
edit sample.rb edit -p MyClass#my_method
|
2013-01-17 22:32:03 -05:00
|
|
|
edit sample.rb --line 105 edit MyClass
|
2013-01-12 12:34:13 -05:00
|
|
|
edit MyClass#my_method edit --ex
|
|
|
|
edit --method edit --ex -p
|
2012-08-11 20:22:29 -04:00
|
|
|
|
|
|
|
https://github.com/pry/pry/wiki/Editor-integration#wiki-Edit_command
|
|
|
|
BANNER
|
|
|
|
|
|
|
|
def options(opt)
|
2013-01-09 15:23:19 -05:00
|
|
|
opt.on :e, :ex, "Open the file that raised the most recent exception (_ex_.file)",
|
2018-10-12 15:09:29 -04:00
|
|
|
optional_argument: true, as: Integer
|
2013-01-09 15:23:19 -05:00
|
|
|
opt.on :i, :in, "Open a temporary file containing the Nth input expression. N may be a range",
|
2018-10-12 15:09:29 -04:00
|
|
|
optional_argument: true, as: Range, default: -1..-1
|
2013-01-09 15:23:19 -05:00
|
|
|
opt.on :t, :temp, "Open an empty temporary file"
|
|
|
|
opt.on :l, :line, "Jump to this line in the opened file",
|
2018-10-12 15:09:29 -04:00
|
|
|
argument: true, as: Integer
|
2013-07-26 19:52:15 -04:00
|
|
|
opt.on :n, :"no-reload", "Don't automatically reload the edited file"
|
2013-01-09 15:23:19 -05:00
|
|
|
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"
|
2013-01-11 14:38:12 -05:00
|
|
|
opt.on :m, :method, "Explicitly edit the _current_ method (when inside a method context)."
|
2012-08-11 20:22:29 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def process
|
2012-12-29 17:37:55 -05:00
|
|
|
if bad_option_combination?
|
2013-01-11 14:38:12 -05:00
|
|
|
raise CommandError, "Only one of --ex, --temp, --in, --method and FILE may be specified."
|
2012-08-11 20:22:29 -04:00
|
|
|
end
|
|
|
|
|
2013-01-02 19:46:11 -05:00
|
|
|
if repl_edit?
|
|
|
|
# code defined in pry, eval'd within pry.
|
|
|
|
repl_edit
|
2012-12-30 21:29:05 -05:00
|
|
|
elsif runtime_patch?
|
2014-08-10 02:00:42 -04:00
|
|
|
# patch code without persisting changes, implies future changes are patches
|
2012-12-30 21:29:05 -05:00
|
|
|
apply_runtime_patch
|
2012-08-11 20:22:29 -04:00
|
|
|
else
|
2013-01-02 19:46:11 -05:00
|
|
|
# code stored in actual files, eval'd at top-level
|
|
|
|
file_edit
|
2012-08-11 20:22:29 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-01-11 14:38:12 -05:00
|
|
|
def repl_edit?
|
|
|
|
!opts.present?(:ex) && !opts.present?(:current) && !opts.present?(:method) &&
|
|
|
|
filename_argument.empty?
|
|
|
|
end
|
|
|
|
|
2013-01-02 19:46:11 -05:00
|
|
|
def repl_edit
|
2014-04-29 03:27:16 -04:00
|
|
|
content = Pry::Editor.new(_pry_).edit_tempfile_with_content(initial_temp_file_content,
|
2012-12-31 13:13:25 -05:00
|
|
|
initial_temp_file_content.lines.count)
|
2013-07-26 19:52:15 -04:00
|
|
|
silence_warnings do
|
|
|
|
eval_string.replace content
|
2012-12-30 23:48:48 -05:00
|
|
|
end
|
2018-03-25 11:43:27 -04:00
|
|
|
Pry.history.push(content)
|
2012-12-30 23:48:48 -05:00
|
|
|
end
|
|
|
|
|
2013-01-15 14:06:44 -05:00
|
|
|
def file_based_exception?
|
|
|
|
opts.present?(:ex) && !opts.present?(:patch)
|
|
|
|
end
|
|
|
|
|
2013-01-11 14:38:12 -05:00
|
|
|
def runtime_patch?
|
2014-08-10 02:33:38 -04:00
|
|
|
!file_based_exception? && (opts.present?(:patch) || previously_patched?(code_object) || pry_method?(code_object))
|
2013-01-11 14:38:12 -05:00
|
|
|
end
|
|
|
|
|
2012-12-30 23:48:48 -05:00
|
|
|
def apply_runtime_patch
|
|
|
|
if patch_exception?
|
2013-01-11 14:38:12 -05:00
|
|
|
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
|
|
|
|
|
2013-01-09 11:26:19 -05:00
|
|
|
def ensure_file_name_is_valid(file_name)
|
2013-01-11 14:38:12 -05:00
|
|
|
raise CommandError, "Cannot find a valid file for #{filename_argument}" if !file_name
|
2012-12-31 13:13:25 -05:00
|
|
|
raise CommandError, "#{file_name} is not a valid file name, cannot edit!" if not_a_real_file?(file_name)
|
2013-01-09 11:26:19 -05:00
|
|
|
end
|
|
|
|
|
2013-01-11 14:38:12 -05:00
|
|
|
def file_and_line_for_current_exception
|
|
|
|
FileAndLineLocator.from_exception(_pry_.last_exception, opts[:ex].to_i)
|
|
|
|
end
|
|
|
|
|
2013-01-09 11:26:19 -05:00
|
|
|
def file_and_line
|
|
|
|
file_name, line = if opts.present?(:current)
|
2013-01-09 13:42:00 -05:00
|
|
|
FileAndLineLocator.from_binding(target)
|
2013-01-09 11:26:19 -05:00
|
|
|
elsif opts.present?(:ex)
|
2013-01-11 14:38:12 -05:00
|
|
|
file_and_line_for_current_exception
|
2013-01-09 11:26:19 -05:00
|
|
|
elsif code_object
|
2013-01-11 14:38:12 -05:00
|
|
|
FileAndLineLocator.from_code_object(code_object, filename_argument)
|
2013-01-09 11:26:19 -05:00
|
|
|
else
|
2013-01-09 13:42:00 -05:00
|
|
|
# when file and line are passed as a single arg, e.g my_file.rb:30
|
2013-01-11 14:38:12 -05:00
|
|
|
FileAndLineLocator.from_filename_argument(filename_argument)
|
2013-01-09 11:26:19 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
[file_name, opts.present?(:line) ? opts[:l].to_i : line]
|
|
|
|
end
|
|
|
|
|
|
|
|
def file_edit
|
|
|
|
file_name, line = file_and_line
|
2013-01-11 14:38:12 -05:00
|
|
|
|
2013-01-09 11:26:19 -05:00
|
|
|
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))
|
2013-01-13 16:09:09 -05:00
|
|
|
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
|
|
|
|
|
2013-01-11 14:38:12 -05:00
|
|
|
def filename_argument
|
2013-01-13 16:09:09 -05:00
|
|
|
args.join(' ')
|
2012-12-30 23:48:48 -05:00
|
|
|
end
|
|
|
|
|
2013-01-11 14:38:12 -05:00
|
|
|
def code_object
|
|
|
|
@code_object ||= !probably_a_file?(filename_argument) &&
|
|
|
|
Pry::CodeObject.lookup(filename_argument, _pry_)
|
2012-12-30 23:15:25 -05:00
|
|
|
end
|
|
|
|
|
2013-01-11 14:38:12 -05:00
|
|
|
def pry_method?(code_object)
|
2012-12-30 23:48:48 -05:00
|
|
|
code_object.is_a?(Pry::Method) &&
|
2013-01-11 14:38:12 -05:00
|
|
|
code_object.pry_method?
|
2012-12-30 21:29:05 -05:00
|
|
|
end
|
|
|
|
|
2014-08-10 02:00:42 -04:00
|
|
|
def previously_patched?(code_object)
|
|
|
|
code_object.is_a?(Pry::Method) && Pry::Method::Patcher.code_for(code_object.source_location.first)
|
|
|
|
end
|
|
|
|
|
2012-12-31 13:13:25 -05:00
|
|
|
def patch_exception?
|
|
|
|
opts.present?(:ex) && opts.present?(:patch)
|
|
|
|
end
|
|
|
|
|
2012-12-31 15:52:06 -05:00
|
|
|
def bad_option_combination?
|
|
|
|
[opts.present?(:ex), opts.present?(:temp),
|
2013-01-11 14:38:12 -05:00
|
|
|
opts.present?(:in), opts.present?(:method), !filename_argument.empty?].count(true) > 1
|
2012-12-31 15:52:06 -05:00
|
|
|
end
|
|
|
|
|
2012-12-31 13:13:25 -05:00
|
|
|
def input_expression
|
2012-08-11 20:22:29 -04:00
|
|
|
case opts[:i]
|
|
|
|
when Range
|
2018-10-19 10:27:37 -04:00
|
|
|
(_pry_.input_ring[opts[:i]] || []).join
|
2017-06-13 17:16:09 -04:00
|
|
|
when Integer
|
2018-10-19 10:27:37 -04:00
|
|
|
_pry_.input_ring[opts[:i]] || ""
|
2012-08-11 20:22:29 -04:00
|
|
|
else
|
2012-08-18 23:58:48 -04:00
|
|
|
raise Pry::CommandError, "Not a valid range: #{opts[:i]}"
|
2012-08-11 20:22:29 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-12-29 17:37:55 -05:00
|
|
|
def reloadable?
|
|
|
|
opts.present?(:reload) || opts.present?(:ex)
|
|
|
|
end
|
|
|
|
|
|
|
|
def never_reload?
|
2014-02-06 20:00:50 -05:00
|
|
|
opts.present?(:'no-reload') || _pry_.config.disable_auto_reload
|
2012-12-29 17:37:55 -05:00
|
|
|
end
|
|
|
|
|
2018-11-04 04:34:24 -05:00
|
|
|
def reload?(file_name = "")
|
2012-12-29 17:37:55 -05:00
|
|
|
(reloadable? || file_name.end_with?(".rb")) && !never_reload?
|
|
|
|
end
|
|
|
|
|
|
|
|
def initial_temp_file_content
|
|
|
|
case
|
|
|
|
when opts.present?(:temp)
|
|
|
|
""
|
|
|
|
when opts.present?(:in)
|
2012-12-31 13:13:25 -05:00
|
|
|
input_expression
|
2012-12-29 17:37:55 -05:00
|
|
|
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 != "" } || ""
|
2012-08-11 20:22:29 -04:00
|
|
|
end
|
2012-12-29 17:37:55 -05:00
|
|
|
end
|
2012-08-11 20:22:29 -04:00
|
|
|
|
2012-12-22 16:56:59 -05:00
|
|
|
def probably_a_file?(str)
|
2014-01-25 16:55:47 -05:00
|
|
|
[".rb", ".c", ".py", ".yml", ".gemspec"].include?(File.extname(str)) ||
|
2012-12-29 17:37:55 -05:00
|
|
|
str =~ /\/|\\/
|
2012-12-22 16:56:59 -05:00
|
|
|
end
|
2012-08-11 20:22:29 -04:00
|
|
|
end
|
2012-12-25 16:35:17 -05:00
|
|
|
|
|
|
|
Pry::Commands.add_command(Pry::Command::Edit)
|
2012-08-11 20:22:29 -04:00
|
|
|
end
|