lib/byebug/commands/frame.rb
# encoding: utf-8 module Byebug # # Mixin to assist command parsing # module FrameFunctions def c_frame?(frame_no) @state.context.frame_binding(frame_no).nil? end def switch_to_frame(frame_no) frame_no >= 0 ? frame_no : Context.stack_size + frame_no end def navigate_to_frame(jump_no) return if jump_no == 0 total_jumps, current_jumps, new_pos = jump_no.abs, 0, @state.frame_pos step = jump_no / total_jumps loop do new_pos += step return new_pos if new_pos < 0 || new_pos >= Context.stack_size next if c_frame?(new_pos) current_jumps += 1 break if current_jumps == total_jumps end new_pos end def adjust_frame(frame_pos, absolute) if absolute abs_frame_pos = switch_to_frame(frame_pos) return errmsg("Can't navigate to c-frame") if c_frame?(abs_frame_pos) else abs_frame_pos = navigate_to_frame(frame_pos) end if abs_frame_pos >= Context.stack_size return errmsg("Can't navigate beyond the oldest frame") elsif abs_frame_pos < 0 return errmsg("Can't navigate beyond the newest frame") end @state.frame_pos = abs_frame_pos @state.file = @state.context.frame_file @state.frame_pos @state.line = @state.context.frame_line @state.frame_pos @state.prev_line = nil ListCommand.new(@state).execute end def get_frame_class(style, pos) frame_class = style == 'short' ? '' : "#{@state.context.frame_class pos}" frame_class == '' ? '' : "#{frame_class}." end def get_frame_block_and_method(pos) frame_deco_regexp = /((?:block(?: \(\d+ levels\))?|rescue) in )?(.+)/ frame_deco_method = "#{@state.context.frame_method pos}" frame_block_and_method = frame_deco_regexp.match(frame_deco_method)[1..2] frame_block_and_method.map { |x| x.nil? ? '' : x } end def get_frame_args(style, pos) args = @state.context.frame_args pos return '' if args.empty? locals = @state.context.frame_locals pos if style == 'long' my_args = args.map do |arg| case arg[0] when :block prefix, default = '&', 'block' when :rest prefix, default = '*', 'args' else prefix, default = '', nil end klass = style == 'long' && arg[1] ? "##{locals[arg[1]].class}" : '' "#{prefix}#{arg[1] || default}#{klass}" end "(#{my_args.join(', ')})" end def get_frame_call(prefix, pos) frame_block, frame_method = get_frame_block_and_method(pos) frame_class = get_frame_class(Setting[:callstyle], pos) frame_args = get_frame_args(Setting[:callstyle], pos) call_str = frame_block + frame_class + frame_method + frame_args max_call_str_size = Setting[:width] - prefix.size if call_str.size > max_call_str_size call_str = call_str[0..max_call_str_size - 5] + '...)' end call_str end def print_backtrace realsize = Context.stack_size calcedsize = @state.context.calced_stack_size if calcedsize != realsize if Byebug.post_mortem? stacksize = calcedsize else errmsg "Byebug's stacksize (#{calcedsize}) should be #{realsize}. " \ "This might be a bug in byebug or ruby's debugging API's\n" stacksize = realsize end else stacksize = calcedsize end (0...stacksize).each do |idx| print_frame(idx) end end require 'pathname' def shortpath(fullpath) components = Pathname(fullpath).each_filename.to_a return File.join(components) if components.size <= 2 File.join('...', components[-3..-1]) end def print_frame(pos, mark_current = true) fullpath = @state.context.frame_file(pos) file = Setting[:fullpath] ? fullpath : shortpath(fullpath) line = @state.context.frame_line(pos) if mark_current frame_str = (pos == @state.frame_pos) ? '--> ' : ' ' else frame_str = '' end frame_str += c_frame?(pos) ? ' ͱ-- ' : '' frame_str += format('#%-2d ', pos) frame_str += get_frame_call frame_str, pos file_line = "at #{CommandProcessor.canonic_file(file)}:#{line}" if frame_str.size + file_line.size + 1 > Setting[:width] frame_str += "\n #{file_line}" else frame_str += " #{file_line}" end puts frame_str end end # # Show current backtrace. # class WhereCommand < Command def regexp /^\s* (?:w(?:here)?|bt|backtrace) \s*$/x end def execute print_backtrace end class << self def names %w(where backtrace bt) end def description %(w[here]|bt|backtrace Display stack frames. Print the entire stack frame. Each frame is numbered; the most recent frame is 0. A frame number can be referred to in the "frame" command; "up" and "down" add or subtract respectively to frame numbers shown. The position of the current frame is marked with -->. C-frames hang from their most immediate Ruby frame to indicate that they are not navigable.) end end end # # Move the current frame up in the backtrace. # class UpCommand < Command def regexp /^\s* u(?:p)? (?:\s+(\S+))? \s*$/x end def execute pos, err = parse_steps(@match[1], 'Up') return errmsg(err) unless pos adjust_frame(pos, false) end class << self def names %w(up) end def description %(up[ count] Move to higher frame.) end end end # # Move the current frame down in the backtrace. # class DownCommand < Command def regexp /^\s* down (?:\s+(\S+))? \s*$/x end def execute pos, err = parse_steps(@match[1], 'Down') return errmsg(err) unless pos adjust_frame(-pos, false) end class << self def names %w(down) end def description %(down[ count] Move to lower frame.) end end end # # Move to specific frames in the backtrace. # class FrameCommand < Command def regexp /^\s* f(?:rame)? (?:\s+(\S+))? \s*$/x end def execute return print_frame @state.frame_pos unless @match[1] pos, err = get_int(@match[1], 'Frame') return errmsg(err) unless pos adjust_frame(pos, true) end class << self def names %w(frame) end def description %(f[rame][ frame-number] Move the current frame to the specified frame number, or the 0 if no frame-number has been given. A negative number indicates position from the other end, so "frame -1" moves to the oldest frame, and "frame 0" moves to the newest frame. Without an argument, the command prints the current stack frame. Since the current position is redisplayed, it may trigger a resyncronization if there is a front end also watching over things.) end end end end