# frozen_string_literal: true class Pry module Byebug # # Wrapper for Byebug.breakpoints that respects our Processor and has better # failure behavior. Acts as an Enumerable. # module Breakpoints extend Enumerable extend self # # Breakpoint in a file:line location # class FileBreakpoint < SimpleDelegator def source_code Pry::Code.from_file(source).around(pos, 3).with_marker(pos) end def to_s "#{source} @ #{pos}" end end # # Breakpoint in a Class#method location # class MethodBreakpoint < SimpleDelegator def initialize(byebug_bp, method) __setobj__ byebug_bp @method = method end def source_code Pry::Code.from_method(Pry::Method.from_str(@method)) end def to_s @method end end def breakpoints @breakpoints ||= [] end # # Adds a method breakpoint. # def add_method(method, expression = nil) validate_expression expression owner, name = method.split(/[\.#]/) byebug_bp = ::Byebug::Breakpoint.add(owner, name.to_sym, expression) bp = MethodBreakpoint.new byebug_bp, method breakpoints << bp bp end # # Adds a file breakpoint. # def add_file(file, line, expression = nil) real_file = (file != Pry.eval_path) raise(ArgumentError, "Invalid file!") if real_file && !File.exist?(file) validate_expression expression path = (real_file ? File.expand_path(file) : file) bp = FileBreakpoint.new ::Byebug::Breakpoint.add(path, line, expression) breakpoints << bp bp end # # Changes the conditional expression for a breakpoint. # def change(id, expression = nil) validate_expression expression breakpoint = find_by_id(id) breakpoint.expr = expression breakpoint end # # Deletes an existing breakpoint with the given ID. # def delete(id) deleted = ::Byebug.started? && ::Byebug::Breakpoint.remove(id) && breakpoints.delete(find_by_id(id)) raise(ArgumentError, "No breakpoint ##{id}") unless deleted end # # Deletes all breakpoints. # def delete_all @breakpoints = [] ::Byebug.breakpoints.clear if ::Byebug.started? end # # Enables a disabled breakpoint with the given ID. # def enable(id) change_status id, true end # # Disables a breakpoint with the given ID. # def disable(id) change_status id, false end # # Disables all breakpoints. # def disable_all each do |breakpoint| breakpoint.enabled = false end end def to_a breakpoints end def size to_a.size end def each(&block) to_a.each(&block) end def last to_a.last end def find_by_id(id) breakpoint = find { |b| b.id == id } raise(ArgumentError, "No breakpoint ##{id}!") unless breakpoint breakpoint end private def change_status(id, enabled = true) breakpoint = find_by_id(id) breakpoint.enabled = enabled breakpoint end def validate_expression(exp) valid = exp && (exp.empty? || !Pry::Code.complete_expression?(exp)) return unless valid raise("Invalid breakpoint conditional: #{expression}") end end end end