lib/byebug/processors/pry_processor.rb
# frozen_string_literal: true
require "byebug/core"
module Byebug
#
# Extends raw byebug's processor.
#
class PryProcessor < CommandProcessor
attr_accessor :pry
extend Forwardable
def_delegators :@pry, :output
def_delegators Pry::Helpers::Text, :bold
def self.start
Byebug.start
Setting[:autolist] = false
Context.processor = self
Byebug.current_context.step_out(5, true)
end
#
# Wrap a Pry REPL to catch navigational commands and act on them.
#
def run(&_block)
return_value = nil
command = catch(:breakout_nav) do # Throws from PryByebug::Commands
return_value = allowing_other_threads { yield }
{} # Nothing thrown == no navigational command
end
# Pry instance to resume after stepping
@pry = command[:pry]
perform(command[:action], command[:options])
return_value
end
#
# Set up a number of navigational commands to be performed by Byebug.
#
def perform(action, options = {})
return unless %i[
backtrace
down
finish
frame
next
step
up
].include?(action)
send("perform_#{action}", options)
end
# --- Callbacks from byebug C extension ---
#
# Called when the debugger wants to stop at a regular line
#
def at_line
resume_pry
end
#
# Called when the debugger wants to stop right before a method return
#
def at_return(_return_value)
resume_pry
end
#
# Called when a breakpoint is hit. Note that `at_line`` is called
# inmediately after with the context's `stop_reason == :breakpoint`, so we
# must not resume the pry instance here
#
def at_breakpoint(breakpoint)
@pry ||= Pry.new
output.puts bold("\n Breakpoint #{breakpoint.id}. ") + n_hits(breakpoint)
expr = breakpoint.expr
return unless expr
output.puts bold("Condition: ") + expr
end
private
def n_hits(breakpoint)
n_hits = breakpoint.hit_count
n_hits == 1 ? "First hit" : "Hit #{n_hits} times."
end
#
# Resume an existing Pry REPL at the paused point.
#
def resume_pry
new_binding = frame._binding
run do
if defined?(@pry) && @pry
@pry.repl(new_binding)
else
@pry = Pry::REPL.start_without_pry_byebug(target: new_binding)
end
end
end
def perform_backtrace(_options)
Byebug::WhereCommand.new(self, "backtrace").execute
resume_pry
end
def perform_next(options)
lines = (options[:lines] || 1).to_i
context.step_over(lines, frame.pos)
end
def perform_step(options)
times = (options[:times] || 1).to_i
context.step_into(times, frame.pos)
end
def perform_finish(*)
context.step_out(1)
end
def perform_up(options)
times = (options[:times] || 1).to_i
Byebug::UpCommand.new(self, "up #{times}").execute
resume_pry
end
def perform_down(options)
times = (options[:times] || 1).to_i
Byebug::DownCommand.new(self, "down #{times}").execute
resume_pry
end
def perform_frame(options)
index = options[:index] ? options[:index].to_i : ""
Byebug::FrameCommand.new(self, "frame #{index}").execute
resume_pry
end
end
end