module Byebug # # Processes commands in regular mode # 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 without_exceptions do puts "INTERNAL ERROR!!! #\{$!\}" puts $!.backtrace.map{|l| "\t#\{l\}"}.join("\n") end end END end def at_breakpoint(_context, breakpoint) n = Byebug.breakpoints.index(breakpoint) + 1 file = self.class.canonic_file(breakpoint.source) line = breakpoint.pos puts "Stopped by breakpoint #{n} at #{file}:#{line}" end protect :at_breakpoint def at_catchpoint(context, excpt) file = self.class.canonic_file(context.frame_file(0)) line = context.frame_line(0) puts "Catchpoint at #{file}:#{line}: `#{excpt}' (#{excpt.class})" end protect :at_catchpoint include ParseFunctions def at_tracing(context, file, line) if file != @last_file || line != @last_line || Setting[:tracing_plus] path = self.class.canonic_file(file) @last_file, @last_line = file, line puts "Tracing: #{path}:#{line} #{get_line(file, line)}" 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) "(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 } [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(/;/).each_with_object([]) do |v, m| if m.empty? m << v else if m.last[-1] == '\\' m.last[-1, 1] = '' m.last << ';' << v else m << v end end 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) puts(state.location) if Setting[:autolist] == 0 until state.proceed? input = if @interface.command_queue.empty? @interface.read_command(prompt(context)) else @interface.command_queue.shift end break unless input 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 # # Autoevals a single command # def one_unknown_cmd(commands, input) unless Setting[:autoeval] return errmsg("Unknown command: \"#{input}\". Try \"help\"") end commands.find { |c| c.is_a?(EvalCommand) }.execute end # # Executes a single byebug command # def one_cmd(commands, context, input) cmd = commands.find { |c| c.match(input) } return one_unknown_cmd(commands, input) unless cmd if context.dead? && !cmd.class.allow_in_post_mortem return errmsg('Command unavailable in post mortem mode.') end cmd.execute end # # Tasks to do before processor loop # def preloop(_commands, context) @context_was_dead = true if context.dead? && !@context_was_dead return unless @context_was_dead puts 'The program finished.' @context_was_dead = false 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, :puts, :confirm def proceed? @proceed end def proceed @proceed = true end def location path = self.class.canonic_file(@file) loc = "#{path} @ #{@line}\n" loc += "#{get_line(@file, @line)}\n" unless ['(irb)', '-e'].include? @file loc end end end end