diff --git a/lib/pry.rb b/lib/pry.rb index 2614109c..37fe005a 100644 --- a/lib/pry.rb +++ b/lib/pry.rb @@ -79,6 +79,7 @@ if RUBY_PLATFORM =~ /mswin/ || RUBY_PLATFORM =~ /mingw/ end require "pry/version" +require "pry/history_array" require "pry/helpers" require "pry/command_set" require "pry/commands" @@ -88,4 +89,4 @@ require "pry/completion" require "pry/plugins" require "pry/core_extensions" require "pry/pry_class" -require "pry/pry_instance" \ No newline at end of file +require "pry/pry_instance" diff --git a/lib/pry/config.rb b/lib/pry/config.rb index acb61c8b..338c91ac 100644 --- a/lib/pry/config.rb +++ b/lib/pry/config.rb @@ -88,6 +88,9 @@ class Pry # `plugins.strict_loading` (Boolean) which toggles whether referring to a non-existent plugin should raise an exception (defaults to `false`) # @return [OpenStruct] attr_accessor :plugins + + # @return [Integer] Amount of results that will be stored into _out_ + attr_accessor :memory_size end end diff --git a/lib/pry/history_array.rb b/lib/pry/history_array.rb new file mode 100644 index 00000000..b6beb179 --- /dev/null +++ b/lib/pry/history_array.rb @@ -0,0 +1,105 @@ +class Pry + # A history array is an array to which you can only add elements. Older + # entries are removed progressively, so that the aray never contains more than + # N elements. + # + # History arrays are used by Pry to store the output of the last commands. + # + # @example + # ary = Pry::HistoryArray.new 10 + # ary << 1 << 2 << 3 + # ary[0] # => 1 + # ary[1] # => 2 + # 10.times { |n| ary << n } + # ary[0] # => nil + # ary[-1] # => 9 + class HistoryArray + include Enumerable + + # @param [Integer] size Maximum amount of objects in the array + def initialize(size) + @max_size = size + + @hash = {} + @count = 0 + end + + # Pushes an object at the end of the array + # @param [Object] value Object to be added + def <<(value) + @hash[@count] = value + + if @hash.size > max_size + @hash.delete(@count - max_size) + end + + @count += 1 + + self + end + + # @overload [](index) + # @param [Integer] index Index of the item to access. + # @return [Object, nil] Item at that index or nil if it has been removed. + # @overload [](index, size) + # @param [Integer] index Index of the first item to access. + # @param [Integer] size Amount of items to access + # @return [Array, nil] The selected items. Nil if index is greater than + # the size of the array. + # @overload [](range) + # @param [Range] range Range of indices to access. + # @return [Array, nil] The selected items. Nil if index is greater than + # the size of the array. + def [](index_or_range, size = nil) + if index_or_range.is_a? Integer + index = convert_index(index_or_range) + + if size + end_index = index + size + index > @count ? nil : (index...[end_index, @count].min).map do |n| + @hash[n] + end + else + @hash[index] + end + else + range = convert_range(index_or_range) + range.begin > @count ? nil : range.map { |n| @hash[n] } + end + end + + # @return [Integer] Amount of objects in the array + def size + @count + end + + def each + ((@count - size)...@count).each do |n| + yield @hash[n] + end + end + + def to_a + ((@count - size)...@count).map { |n| @hash[n] } + end + + def inspect + "#<#{self.class} size=#{size} first=#{@count - size} max_size=#{max_size}>" + end + + # @return [Integer] Maximum amount of objects in the array + attr_reader :max_size + + private + def convert_index(n) + n >= 0 ? n : @count + n + end + + def convert_range(range) + end_index = convert_index(range.end) + end_index += 1 unless range.exclude_end? + + Range.new(convert_index(range.begin), [end_index, @count].min, true) + end + end +end diff --git a/lib/pry/pry_class.rb b/lib/pry/pry_class.rb index 5a2fe3d1..75c9062e 100644 --- a/lib/pry/pry_class.rb +++ b/lib/pry/pry_class.rb @@ -69,7 +69,7 @@ class Pry def_delegators :@plugin_manager, :plugins, :load_plugins, :locate_plugins delegate_accessors :@config, :input, :output, :commands, :prompt, :print, :exception_handler, - :hooks, :color, :pager, :editor + :hooks, :color, :pager, :editor, :memory_size end # Load the rc files given in the `Pry::RC_FILES` array. @@ -199,6 +199,8 @@ class Pry config.history.save = true config.history.load = true config.history.file = File.expand_path("~/.pry_history") + + config.memory_size = 100 end # Set all the configurable options back to their default values diff --git a/lib/pry/pry_instance.rb b/lib/pry/pry_instance.rb index 8a0ca416..d69a8bd6 100644 --- a/lib/pry/pry_instance.rb +++ b/lib/pry/pry_instance.rb @@ -26,6 +26,7 @@ class Pry # component of the REPL. (see print.rb) def initialize(options={}) refresh(options) + @command_processor = CommandProcessor.new(self) end @@ -38,7 +39,7 @@ class Pry attributes = [ :input, :output, :commands, :print, :exception_handler, :hooks, :custom_completions, - :prompt + :prompt, :memory_size ] attributes.each do |attribute| @@ -48,6 +49,7 @@ class Pry defaults.merge!(options).each do |key, value| send "#{key}=", value end + true end @@ -71,6 +73,17 @@ class Pry end end + # @return [Integer] The maximum amount of objects remembered by the _in_ and + # _out_ arrays. Defaults to 100. + def memory_size + @output_array.max_size + end + + def memory_size=(size) + @input_array = Pry::HistoryArray.new(size) + @output_array = Pry::HistoryArray.new(size) + end + # Get nesting data. # This method should not need to be accessed directly. # @return [Array] The unparsed nesting information. @@ -111,8 +124,13 @@ class Pry Pry.active_instance = self # Make sure special locals exist + target.eval("_in_ = ::Pry.active_instance.instance_eval { @input_array }") + target.eval("_out_ = ::Pry.active_instance.instance_eval { @output_array }") + set_active_instance(target) + @input_array << nil # add empty input so _in_ and _out_ match set_last_result(Pry.last_result, target) + self.session_target = target end @@ -193,19 +211,28 @@ class Pry Readline.completion_proc = Pry::InputCompleter.build_completion_proc target, instance_eval(&custom_completions) end + # save the pry instance to active_instance + Pry.active_instance = self + + target.eval("_in_ = ::Pry.active_instance.instance_eval { @input_array }") + target.eval("_out_ = ::Pry.active_instance.instance_eval { @output_array }") + @last_result_is_exception = false set_active_instance(target) - expr = r(target) - Pry.line_buffer.push(*expr.each_line) - set_last_result(target.eval(expr, Pry.eval_path, Pry.current_line), target) + code = r(target) + + Pry.line_buffer.push(*code.each_line) + res = set_last_result(target.eval(code, Pry.eval_path, Pry.current_line), target) + @input_array << code + res rescue SystemExit => e exit rescue Exception => e @last_result_is_exception = true set_last_exception(e, target) ensure - Pry.current_line += expr.each_line.count if expr + Pry.current_line += code.each_line.count if code end # Perform a read. @@ -292,6 +319,7 @@ class Pry # @param [Binding] target The binding to set `_` on. def set_last_result(result, target) Pry.last_result = result + @output_array << result target.eval("_ = ::Pry.last_result") end diff --git a/test/test_history_array.rb b/test/test_history_array.rb new file mode 100644 index 00000000..fecd3555 --- /dev/null +++ b/test/test_history_array.rb @@ -0,0 +1,65 @@ +require 'helper' + +describe Pry::HistoryArray do + before do + @array = Pry::HistoryArray.new 10 + end + + it 'should have a maximum size specifed at creation time' do + @array.max_size.should == 10 + end + + it 'should be able to be added objects to' do + @array << 1 << 2 << 3 + @array.size.should == 3 + @array.to_a.should == [1, 2, 3] + end + + it 'should be able to access single elements' do + @array << 1 << 2 << 3 + @array[2].should == 3 + end + + it 'should be able to access negative indices' do + @array << 1 << 2 << 3 + @array[-1].should == 3 + end + + it 'should be able to access ranges' do + @array << 1 << 2 << 3 << 4 + @array[1..2].should == [2, 3] + end + + it 'should be able to access ranges starting from a negative index' do + @array << 1 << 2 << 3 << 4 + @array[-2..3].should == [3, 4] + end + + it 'should be able to access ranges ending at a negative index' do + @array << 1 << 2 << 3 << 4 + @array[2..-1].should == [3, 4] + end + + it 'should be able to access ranges using only negative indices' do + @array << 1 << 2 << 3 << 4 + @array[-2..-1].should == [3, 4] + end + + it 'should be able to use range where end is excluded' do + @array << 1 << 2 << 3 << 4 + @array[-2...-1].should == [3] + end + + it 'should be able to access slices using a size' do + @array << 1 << 2 << 3 << 4 + @array[-3, 2].should == [2, 3] + end + + it 'should remove older entries' do + 11.times { |n| @array << n } + + @array[0].should == nil + @array[1].should == 1 + @array[10].should == 10 + end +end diff --git a/test/test_pry.rb b/test/test_pry.rb index 3669f079..e4f0e740 100644 --- a/test/test_pry.rb +++ b/test/test_pry.rb @@ -143,6 +143,56 @@ describe Pry do end end + describe "history arrays" do + it 'sets _ to the last result' do + res = [] + input = InputTester.new *[":foo", "self << _", "42", "self << _"] + pry = Pry.new(:input => input, :output => Pry::NullOutput) + pry.repl(res) + + res.should == [:foo, 42] + end + + it 'sets _out_ to an array with the result' do + res = {} + input = InputTester.new *[":foo", "42", "self[:res] = _out_"] + pry = Pry.new(:input => input, :output => Pry::NullOutput) + pry.repl(res) + + res[:res].should.be.kind_of Pry::HistoryArray + res[:res][1..2].should == [:foo, 42] + end + + it 'sets _in_ to an array with the entered lines' do + res = {} + input = InputTester.new *[":foo", "42", "self[:res] = _in_"] + pry = Pry.new(:input => input, :output => Pry::NullOutput) + pry.repl(res) + + res[:res].should.be.kind_of Pry::HistoryArray + res[:res][1..2].should == [":foo\n", "42\n"] + end + + it 'uses 100 as the size of _in_ and _out_' do + res = [] + input = InputTester.new *["self << _out_.max_size << _in_.max_size"] + pry = Pry.new(:input => input, :output => Pry::NullOutput) + pry.repl(res) + + res.should == [100, 100] + end + + it 'can change the size of the history arrays' do + res = [] + input = InputTester.new *["self << _out_.max_size << _in_.max_size"] + pry = Pry.new(:input => input, :output => Pry::NullOutput, + :memory_size => 1000) + pry.repl(res) + + res.should == [1000, 1000] + end + end + describe "test loading rc files" do before do