module Byebug class CommandProcessor < Processor attr_reader :display def initialize(interface = LocalInterface.new) super(interface) @display = [] @mutex = Mutex.new @last_cmd = nil # To allow empty (just ) commands @last_file = nil # Filename the last time we stopped @last_line = nil # Line number the last time we stopped @context_was_dead = false # Assume we haven't started. end def interface=(interface) @mutex.synchronize do @interface.close if @interface @interface = interface end end require 'pathname' # For cleanpath # # Regularize file name. # # This is also used as a common funnel place if basename is desired or if we # are working remotely and want to change the basename. Or we are eliding # filenames. def self.canonic_file(filename) return filename if ['(irb)', '-e'].include?(filename) # For now we want resolved filenames if Setting[:basename] File.basename(filename) else Pathname.new(filename).cleanpath.to_s end end def self.protect(mname) alias_method "__#{mname}", mname module_eval <<-END, __FILE__, __LINE__+1 def #{mname}(*args) @mutex.synchronize do return unless @interface __#{mname}(*args) end rescue IOError, SystemCallError @interface.close rescue SignalException raise rescue print "INTERNAL ERROR!!! #\{$!\}\n" rescue nil print $!.backtrace.map{|l| "\t#\{l\}"}.join("\n") rescue nil end END end def at_breakpoint(context, breakpoint) n = Byebug.breakpoints.index(breakpoint) + 1 file = CommandProcessor.canonic_file(breakpoint.source) line = breakpoint.pos print "Stopped by breakpoint #{n} at #{file}:#{line}\n" end protect :at_breakpoint def at_catchpoint(context, excpt) file = CommandProcessor.canonic_file(context.frame_file(0)) line = context.frame_line(0) print "Catchpoint at %s:%d: `%s' (%s)\n", file, line, excpt, excpt.class end protect :at_catchpoint def at_tracing(context, file, line) if file != @last_file || line != @last_line || Setting[:tracing_plus] @last_file, @last_line = file, line print "Tracing: #{CommandProcessor.canonic_file(file)}:#{line} " \ "#{Byebug.line_at(file,line)}\n" end always_run(context, file, line, 2) end protect :at_tracing def at_line(context, file, line) Byebug.source_reload if Setting[:autoreload] process_commands(context, file, line) end protect :at_line def at_return(context, file, line) process_commands(context, file, line) end protect :at_return private # # Prompt shown before reading a command. # def prompt(context) return "(byebug#{context.dead? ? ':post-mortem' : ''}) " end # # Run commands everytime. # # For example display commands or possibly the list or irb in an # "autolist" or "autoirb". # # @return List of commands acceptable to run bound to the current state # def always_run(context, file, line, run_level) cmds = Command.commands state = State.new(cmds, context, @display, file, @interface, line) # Change default when in irb or code included in command line Setting[:autolist] = false if ['(irb)', '-e'].include?(file) # Bind commands to the current state. commands = cmds.map { |cmd| cmd.new(state) } commands.select { |cmd| cmd.class.always_run >= run_level } .each { |cmd| cmd.execute } return state, commands end # # Splits a command line of the form "cmd1 ; cmd2 ; ... ; cmdN" into an # array of commands: [cmd1, cmd2, ..., cmdN] # def split_commands(cmd_line) cmd_line.split(/;/).inject([]) do |m, v| if m.empty? m << v else if m.last[-1] == ?\\ m.last[-1,1] = '' m.last << ';' << v else m << v end end m end end # # Handle byebug commands. # def process_commands(context, file, line) state, commands = always_run(context, file, line, 1) if Setting[:testing] Thread.current.thread_variable_set('state', state) else Thread.current.thread_variable_set('state', nil) end preloop(commands, context) print state.location if Setting[:autolist] == 0 while !state.proceed? input = @interface.command_queue.empty? ? @interface.read_command(prompt(context)) : @interface.command_queue.shift break unless input catch(:debug_error) do if input == "" next unless @last_cmd input = @last_cmd else @last_cmd = input end split_commands(input).each do |cmd| one_cmd(commands, context, cmd) end end end end # # Executes a single byebug command # def one_cmd(commands, context, input) if cmd = commands.find { |c| c.match(input) } if context.dead? && !cmd.class.allow_in_post_mortem errmsg "Command unavailable in post mortem mode.\n" else cmd.execute end else unknown_cmd = commands.find { |c| c.class.unknown } if unknown_cmd unknown_cmd.execute else errmsg "Unknown command: \"#{input}\". Try \"help\".\n" end end end # # Tasks to do before processor loop # def preloop(commands, context) @context_was_dead = true if context.dead? and not @context_was_dead if @context_was_dead print "The program finished.\n" @context_was_dead = false end end class State attr_accessor :commands, :context, :display, :file, :frame_pos attr_accessor :interface, :line, :previous_line def initialize(commands, context, display, file, interface, line) @commands, @context, @display = commands, context, display @file, @interface, @line = file, interface, line @frame_pos, @previous_line, @proceed = 0, nil, false end extend Forwardable def_delegators :@interface, :errmsg, :print, :confirm def proceed? @proceed end def proceed @proceed = true end def location loc = "#{CommandProcessor.canonic_file(@file)} @ #{@line}\n" loc += "#{Byebug.line_at(@file, @line)}\n" unless ['(irb)', '-e'].include? @file loc end end end # class CommandProcessor end