diff --git a/lib/pry/code.rb b/lib/pry/code.rb index c8c9c52d..36cd6c9a 100644 --- a/lib/pry/code.rb +++ b/lib/pry/code.rb @@ -61,6 +61,8 @@ class Pry def from_file(filename, code_type = type_from_filename(filename)) code = if filename == Pry.eval_path Pry.line_buffer.drop(1) + elsif Pry::Method::Patcher.code_for(filename) + Pry::Method::Patcher.code_for(filename) else File.read(abs_path(filename)) end diff --git a/lib/pry/commands/edit.rb b/lib/pry/commands/edit.rb index bddecaa5..7969c01d 100644 --- a/lib/pry/commands/edit.rb +++ b/lib/pry/commands/edit.rb @@ -1,6 +1,5 @@ class Pry class Command::Edit < Pry::ClassCommand - require 'pry/commands/edit/method_patcher' require 'pry/commands/edit/exception_patcher' require 'pry/commands/edit/file_and_line_locator' @@ -83,7 +82,7 @@ class Pry ExceptionPatcher.new(_pry_, state, file_and_line_for_current_exception).perform_patch else if code_object.is_a?(Pry::Method) - MethodPatcher.new(_pry_, code_object).perform_patch + code_object.redefine Pry::Editor.edit_tempfile_with_content(code_object.source) else raise NotImplementedError, "Cannot yet patch #{code_object} objects!" end diff --git a/lib/pry/method.rb b/lib/pry/method.rb index 1f267f5e..40bcda40 100644 --- a/lib/pry/method.rb +++ b/lib/pry/method.rb @@ -19,6 +19,7 @@ class Pry class Method require 'pry/method/weird_method_locator' require 'pry/method/disowned' + require 'pry/method/patcher' extend Helpers::BaseHelpers include Helpers::BaseHelpers @@ -263,26 +264,18 @@ class Pry def source @source ||= case source_type when :c - info = pry_doc_info - if info and info.source - code = strip_comments_from_c_code(info.source) - end + c_source when :ruby - # clone of MethodSource.source_helper that knows to use our - # hacked version of source_location for rbx core methods, and - # our input buffer for methods defined in (pry) - file, line = *source_location - raise SourceNotFoundError, "Could not locate source for #{name_with_owner}!" unless file - - begin - code = Pry::Code.from_file(file).expression_at(line) - rescue SyntaxError => e - raise MethodSource::SourceNotFoundError.new(e.message) - end - strip_leading_whitespace(code) + ruby_source end end + # Update the live copy of the method's source. + def redefine(source) + Patcher.new(self).patch_in_ram source + Pry::Method(owner.instance_method(name)) + end + # Can we get the source code for this method? # @return [Boolean] def source? @@ -547,5 +540,27 @@ class Pry nil end + + def c_source + info = pry_doc_info + if info and info.source + strip_comments_from_c_code(info.source) + end + end + + def ruby_source + # clone of MethodSource.source_helper that knows to use our + # hacked version of source_location for rbx core methods, and + # our input buffer for methods defined in (pry) + file, line = *source_location + raise SourceNotFoundError, "Could not locate source for #{name_with_owner}!" unless file + + begin + code = Pry::Code.from_file(file).expression_at(line) + rescue SyntaxError => e + raise MethodSource::SourceNotFoundError.new(e.message) + end + strip_leading_whitespace(code) + end end end diff --git a/lib/pry/commands/edit/method_patcher.rb b/lib/pry/method/patcher.rb similarity index 62% rename from lib/pry/commands/edit/method_patcher.rb rename to lib/pry/method/patcher.rb index eac69e66..ef09722e 100644 --- a/lib/pry/commands/edit/method_patcher.rb +++ b/lib/pry/method/patcher.rb @@ -1,37 +1,38 @@ class Pry - class Command::Edit - class MethodPatcher - attr_accessor :_pry_ - attr_accessor :code_object + class Method + class Patcher + attr_accessor :method - def initialize(_pry_, code_object) - @_pry_ = _pry_ - @code_object = code_object + @@source_cache = {} + + def initialize(method) + @method = method + end + + def self.code_for(filename) + @@source_cache[filename] end # perform the patch - def perform_patch - if code_object.alias? + def patch_in_ram(source) + if method.alias? with_method_transaction do - _pry_.evaluate_ruby patched_code + redefine source end else - _pry_.evaluate_ruby patched_code + redefine source end end private - def patched_code - @patched_code ||= wrap(Pry::Editor.edit_tempfile_with_content(adjusted_lines)) + def redefine(source) + @@source_cache[cache_key] = source + TOPLEVEL_BINDING.eval wrap(source), cache_key end - # The method code adjusted so that the first line is rewritten - # so that def self.foo --> def foo - def adjusted_lines - lines = code_object.source.lines.to_a - lines[0] = definition_line_for_owner(lines.first) - lines + def cache_key + "pry-redefined(0x#{method.owner.object_id.to_s(16)}##{method.name})" end # Run some code ensuring that at the end target#meth_name will not have changed. @@ -45,17 +46,17 @@ class Pry # @param [Module] target The owner of the method def with_method_transaction - temp_name = "__pry_#{code_object.original_name}__" - co = code_object - code_object.owner.class_eval do - alias_method temp_name, co.original_name + temp_name = "__pry_#{method.original_name}__" + method = self.method + method.owner.class_eval do + alias_method temp_name, method.original_name yield - alias_method co.name, co.original_name - alias_method co.original_name, temp_name + alias_method method.name, method.original_name + alias_method method.original_name, temp_name end ensure - co.send(:remove_method, temp_name) rescue nil + method.send(:remove_method, temp_name) rescue nil end # Update the definition line so that it can be eval'd directly on the Method's @@ -70,11 +71,11 @@ class Pry # # @param String The original definition line. e.g. def self.foo(bar, baz=1) # @return String The new definition line. e.g. def foo(bar, baz=1) - def definition_line_for_owner(line) - if line =~ /^def (?:.*?\.)?#{Regexp.escape(code_object.original_name)}(?=[\(\s;]|$)/ - "def #{code_object.original_name}#{$'}" + def definition_for_owner(line) + if line =~ /\Adef (?:.*?\.)?#{Regexp.escape(method.original_name)}(?=[\(\s;]|$)/ + "def #{method.original_name}#{$'}" else - raise CommandError, "Could not find original `def #{code_object.original_name}` line to patch." + raise CommandError, "Could not find original `def #{method.original_name}` line to patch." end end @@ -87,15 +88,16 @@ class Pry # Update the source code so that when it has the right owner when eval'd. # - # This (combined with definition_line_for_owner) is backup for the case that + # This (combined with definition_for_owner) is backup for the case that # wrap_for_nesting fails, to ensure that the method will stil be defined in # the correct place. # # @param [String] source The source to wrap # @return [String] def wrap_for_owner(source) - Pry.current[:pry_owner] = code_object.owner - "Pry.current[:pry_owner].class_eval do\n#{source}\nend" + Pry.current[:pry_owner] = method.owner + owner_source = definition_for_owner(source) + "Pry.current[:pry_owner].class_eval do; #{owner_source}\nend" end # Update the new source code to have the correct Module.nesting. @@ -111,9 +113,9 @@ class Pry # @param [String] source The source to wrap. # @return [String] def wrap_for_nesting(source) - nesting = Pry::Code.from_file(code_object.source_file).nesting_at(code_object.source_line) + nesting = Pry::Code.from_file(method.source_file).nesting_at(method.source_line) - (nesting + [source] + nesting.map{ "end" } + [""]).join("\n") + (nesting + [source] + nesting.map{ "end" } + [""]).join(";") rescue Pry::Indent::UnparseableNestingError source end diff --git a/spec/method/patcher_spec.rb b/spec/method/patcher_spec.rb new file mode 100644 index 00000000..14d59441 --- /dev/null +++ b/spec/method/patcher_spec.rb @@ -0,0 +1,27 @@ +require 'helper' + +describe Pry::Method::Patcher do + + before do + @x = Object.new + def @x.test; :before; end + @method = Pry::Method(@x.method(:test)) + end + + it "should change the behaviour of the method" do + @x.test.should == :before + @method.redefine "def @x.test; :after; end\n" + @x.test.should == :after + end + + it "should return a new method with new source" do + @method.source.strip.should == "def @x.test; :before; end" + @method.redefine("def @x.test; :after; end\n"). + source.strip.should == "def @x.test; :after; end" + end + + it "should change the source of new Pry::Method objects" do + @method.redefine "def @x.test; :after; end\n" + Pry::Method(@x.method(:test)).source.strip.should == "def @x.test; :after; end" + end +end