diff --git a/lib/pry.rb b/lib/pry.rb index 718e3e42..96df298f 100644 --- a/lib/pry.rb +++ b/lib/pry.rb @@ -104,23 +104,22 @@ class Pry end, ] - # Deal with the ^D key being pressed, different behaviour in - # different cases: - # 1) In an expression - behave like `!` command (clear input buffer) - # 2) At top-level session - behave like `exit command (break out of repl loop) - # 3) In a nested session - behave like `cd ..` (pop a binding) + # Deal with the ^D key being pressed. Different behaviour in different cases: + # 1. In an expression behave like `!` command. + # 2. At top-level session behave like `exit` command. + # 3. In a nested session behave like `cd ..`. DEFAULT_CONTROL_D_HANDLER = proc do |eval_string, _pry_| if !eval_string.empty? - # Clear input buffer. - eval_string.replace("") + eval_string.replace('') # Clear input buffer. elsif _pry_.binding_stack.one? - # ^D at top-level breaks out of a REPL loop. _pry_.binding_stack.clear throw(:breakout) else - # Otherwise, saves current binding stack as old stack and pops last - # binding out of binding stack (the old stack still has that binding). - _pry_.command_state["cd"].old_stack = _pry_.binding_stack.dup + # Store the entire binding stack before popping. Useful for `cd -`. + if _pry_.command_state['cd'].nil? + _pry_.command_state['cd'] = OpenStruct.new + end + _pry_.command_state['cd'].old_stack = _pry_.binding_stack.dup _pry_.binding_stack.pop end end diff --git a/spec/control_d_handler_spec.rb b/spec/control_d_handler_spec.rb index 51a38d6d..01a8adcd 100644 --- a/spec/control_d_handler_spec.rb +++ b/spec/control_d_handler_spec.rb @@ -1,45 +1,58 @@ require 'helper' describe Pry::DEFAULT_CONTROL_D_HANDLER do - describe 'control-d press' do + + describe "control-d press" do + before do + # Simulates a ^D press. @control_d = "Pry::DEFAULT_CONTROL_D_HANDLER.call('', _pry_)" - @binding_stack = "self.binding_stack = _pry_.binding_stack.dup" end - describe 'in an expression' do - it 'should clear out passed string' do - str = "hello world" + describe "in an expression" do + it "should clear out passed string" do + str = 'hello world' Pry::DEFAULT_CONTROL_D_HANDLER.call(str, nil) - str.should == "" + str.should == '' end end - describe 'at top-level session' do - it 'should break out of a REPL loop' do - instance = nil - redirect_pry_io(InputTester.new(@control_d)) do - instance = Pry.new - instance.repl + + describe "at top-level session" do + it "breaks out of a REPL" do + pry_tester(0).simulate_repl do |t| + t.eval @control_d + end.should == nil + end + end + + describe "in a nested session" do + it "pops last binding from the binding stack" do + pry_tester(0).simulate_repl { |t| + t.eval 'cd :foo' + t.eval('_pry_.binding_stack.size').should == 2 + t.eval(@control_d) + t.eval('_pry_.binding_stack.size').should == 1 + t.eval 'exit-all' + } + end + + it "breaks out of the parent session" do + pry_tester(:outer).simulate_repl do |o| + o.context = :inner + o.simulate_repl { |i| + i.eval('_pry_.current_context.eval("self")').should == :inner + i.eval('_pry_.binding_stack.size').should == 2 + i.eval @control_d + i.eval('_pry_.binding_stack.size').should == 1 + i.eval('_pry_.current_context.eval("self")').should == :outer + i.eval 'throw :breakout' + } + o.eval 'exit-all' end - - instance.binding_stack.should.be.empty end end - describe 'in a nested session' do - it 'should pop last binding from the binding stack' do - base = OpenStruct.new - base.obj = OpenStruct.new - - redirect_pry_io(InputTester.new("cd obj", "self.stack_size = _pry_.binding_stack.size", - @control_d, "self.stack_size = _pry_.binding_stack.size", "exit-all")) do - Pry.start(base) - end - - base.obj.stack_size.should == 2 - base.stack_size.should == 1 - end - end end + end