lib/debug/session.rb in debug-1.0.0.beta6 vs lib/debug/session.rb in debug-1.0.0.beta7
- old
+ new
@@ -99,10 +99,14 @@
end
}
@tp_thread_begin.enable
end
+ def active?
+ @ui ? true : false
+ end
+
def reset_ui ui
@ui.close
@ui = ui
@management_threads << @ui.reader_thread if @ui.respond_to? :reader_thread
end
@@ -170,12 +174,15 @@
dap_event ev_args # server.rb
wait_command_loop tc
end
end
ensure
+ @tp_load_script.disable
+ @tp_thread_begin.disable
@bps.each{|k, bp| bp.disable}
@th_clients.each{|th, thc| thc.close}
+ @ui = nil
end
def add_preset_commands name, cmds, kick: true, continue: true
cs = cmds.map{|c|
c = c.strip.gsub(/\A\s*\#.*/, '').strip
@@ -335,14 +342,18 @@
# * Set breakpoint on `<file>:<line>`.
# * `b[reak] <class>#<name>`
# * Set breakpoint on the method `<class>#<name>`.
# * `b[reak] <expr>.<name>`
# * Set breakpoint on the method `<expr>.<name>`.
- # * `b[reak] ... if <expr>`
+ # * `b[reak] ... if: <expr>`
# * break if `<expr>` is true at specified location.
- # * `b[reak] if <expr>`
- # * break if `<expr>` is true at any lines.
+ # * `b[reak] ... pre: <command>`
+ # * break and run `<command>` before stopping.
+ # * `b[reak] ... do: <command>`
+ # * break and run `<command>`, and continue.
+ # * `b[reak] if: <expr>`
+ # * break if: `<expr>` is true at any lines.
# * Note that this feature is super slow.
when 'b', 'break'
if arg == nil
show_bps
return :retry
@@ -426,12 +437,29 @@
### Information
# * `bt` or `backtrace`
# * Show backtrace (frame) information.
+ # * `bt <num>` or `backtrace <num>`
+ # * Only shows first `<num>` frames.
+ # * `bt /regexp/` or `backtrace /regexp/`
+ # * Only shows frames with method name or location info that matches `/regexp/`.
+ # * `bt <num> /regexp/` or `backtrace <num> /regexp/`
+ # * Only shows first `<num>` frames with method name or location info that matches `/regexp/`.
when 'bt', 'backtrace'
- @tc << [:show, :backtrace]
+ case arg
+ when /\A(\d+)\z/
+ @tc << [:show, :backtrace, arg.to_i, nil]
+ when /\A\/(.*)\/\z/
+ pattern = $1
+ @tc << [:show, :backtrace, nil, Regexp.compile(pattern)]
+ when /\A(\d+)\s+\/(.*)\/\z/
+ max, pattern = $1, $2
+ @tc << [:show, :backtrace, max.to_i, Regexp.compile(pattern)]
+ else
+ @tc << [:show, :backtrace, nil, nil]
+ end
# * `l[ist]`
# * Show current frame's source code.
# * Next `list` command shows the successor lines.
# * `l[ist] -`
@@ -607,10 +635,25 @@
else
@ui.puts "unknown thread command: #{arg}"
end
return :retry
+ ### Configuration
+ # * `config`
+ # * Show all configuration with description.
+ # * `config <name>`
+ # * Show current configuration of <name>.
+ # * `config set <name> <val>` or `config <name> = <val>`
+ # * Set <name> to <val>.
+ # * `config append <name> <val>` or `config <name> << <val>`
+ # * Append `<val>` to `<name>` if it is an array.
+ # * `config unset <name>`
+ # * Set <name> to default.
+ when 'config'
+ config_command arg
+ return :retry
+
### Help
# * `h[elp]`
# * Show help for all commands.
# * `h[elp] <command>`
@@ -638,10 +681,77 @@
@ui.puts "[REPL ERROR] #{e.inspect}"
@ui.puts e.backtrace.map{|e| ' ' + e}
return :retry
end
+ def config_show key
+ key = key.to_sym
+ if CONFIG_SET[key]
+ v = CONFIG[key]
+ kv = "#{key} = #{v.nil? ? '(default)' : v.inspect}"
+ desc = CONFIG_SET[key][1]
+ line = "%-30s \# %s" % [kv, desc]
+ if line.size > SESSION.width
+ @ui.puts "\# #{desc}\n#{kv}"
+ else
+ @ui.puts line
+ end
+ else
+ @ui.puts "Unknown configuration: #{key}. 'config' shows all configurations."
+ end
+ end
+
+ def config_set key, val, append: false
+ if CONFIG_SET[key = key.to_sym]
+ begin
+ if append
+ DEBUGGER__.append_config(key, val)
+ else
+ DEBUGGER__.set_config({key => val})
+ end
+ rescue => e
+ @ui.puts e.message
+ end
+ end
+
+ config_show key
+ end
+
+ def config_command arg
+ case arg
+ when nil
+ CONFIG_SET.each do |k, _|
+ config_show k
+ end
+
+ when /\Aunset\s+(.+)\z/
+ if CONFIG_SET[key = $1.to_sym]
+ DEBUGGER__.set_config({key => nil})
+ end
+ config_show key
+
+ when /\A(\w+)\s*=\s*(.+)\z/
+ config_set $1, $2
+
+ when /\A\s*set\s+(\w+)\s+(.+)\z/
+ config_set $1, $2
+
+ when /\A(\w+)\s*<<\s*(.+)\z/
+ config_set $1, $2, append: true
+
+ when /\A\s*append\s+(\w+)\s+(.+)\z/
+ config_set $1, $2
+
+ when /\A(\w+)\z/
+ config_show $1
+
+ else
+ @ui.puts "Can not parse parameters: #{arg}"
+ end
+ end
+
+
def cancel_auto_continue
if @preset_command&.auto_continue
@preset_command.auto_continue = false
end
end
@@ -760,33 +870,41 @@
return [arg, del_bp]
end
end
end
- def repl_add_breakpoint arg
- arg.strip!
+ BREAK_KEYWORDS = %w(if: do: pre:).freeze
- case arg
- when /\Aif\s+(.+)\z/
- cond = $1
- when /(.+?)\s+if\s+(.+)\z/
- sig = $1
- cond = $2
- else
- sig = arg
- end
+ def parse_break arg
+ mode = :sig
+ expr = Hash.new{|h, k| h[k] = []}
+ arg.split(' ').each{|w|
+ if BREAK_KEYWORDS.any?{|pat| w == pat}
+ mode = w[0..-2].to_sym
+ else
+ expr[mode] << w
+ end
+ }
+ expr.default_proc = nil
+ expr.transform_values{|v| v.join(' ')}
+ end
- case sig
+ def repl_add_breakpoint arg
+ expr = parse_break arg.strip
+ cond = expr[:if]
+ cmd = ['break', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
+
+ case expr[:sig]
when /\A(\d+)\z/
- add_line_breakpoint @tc.location.path, $1.to_i, cond: cond
+ add_line_breakpoint @tc.location.path, $1.to_i, cond: expr[:if], command: cmd
when /\A(.+)[:\s+](\d+)\z/
- add_line_breakpoint $1, $2.to_i, cond: cond
+ add_line_breakpoint $1, $2.to_i, cond: expr[:if], command: cmd
when /\A(.+)([\.\#])(.+)\z/
- @tc << [:breakpoint, :method, $1, $2, $3, cond]
+ @tc << [:breakpoint, :method, $1, $2, $3, expr[:if], cmd]
return :noretry
when nil
- add_check_breakpoint cond
+ add_check_breakpoint expr[:if]
else
@ui.puts "Unknown breakpoint format: #{arg}"
@ui.puts
show_help 'b'
end
@@ -906,10 +1024,11 @@
end
## event
def on_load iseq, src
+ DEBUGGER__.info "Load #{iseq.absolute_path || iseq.path}"
@sr.add iseq, src
pending_line_breakpoints do |bp|
if bp.path == (iseq.absolute_path || iseq.path)
bp.try_activate
@@ -918,21 +1037,21 @@
end
# breakpoint management
def add_breakpoint bp
+ # don't repeat commands that add breakpoints
+ @repl_prev_line = nil
+
if @bps.has_key? bp.key
unless bp.duplicable?
@ui.puts "duplicated breakpoint: #{bp}"
bp.disable
end
else
@bps[bp.key] = bp
end
-
- # don't repeat commands that add breakpoints
- @repl_prev_line = nil
end
def rehash_bps
bps = @bps.values
@bps.clear
@@ -999,11 +1118,11 @@
@bps.each{|k, bp|
case bp
when MethodBreakpoint
if bp.method.nil?
if bp.sig_method_name == mid.to_s
- bp.try_enable(quiet: true)
+ bp.try_enable(added: true)
end
end
unresolved = true unless bp.enabled?
end
@@ -1137,24 +1256,22 @@
# default breakpoints
# ::DEBUGGER__.add_catch_breakpoint 'RuntimeError'
Binding.module_eval do
- def bp command: nil, nonstop: nil
- if command
- cmds = command.split(";;")
- # nonstop
- # nil, true -> auto_continue
- # false -> stop
- SESSION.add_preset_commands 'binding.bp(command:)', cmds,
- kick: false,
- continue: nonstop == false ? false : true
+ def break pre: nil, do: nil
+ return unless SESSION.active?
+
+ if pre || (do_expr = binding.local_variable_get(:do))
+ cmds = ['binding.break', pre, do_expr]
end
- ::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1, oneshot: true
+ ::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1, oneshot: true, command: cmds
true
end
+ alias b break
+ # alias bp break
end
load_rc
end
end
@@ -1260,14 +1377,35 @@
else
str
end
end
- def self.warn msg, level = :warn
- case level
- when :warn
- STDERR.puts "DEBUGGER: #{msg}" unless CONFIG[:quiet]
- when :error
- STDERR.puts "DEBUGGER: #{msg}"
+ LOG_LEVELS = {
+ UNKNOWN: 0,
+ FATAL: 1,
+ ERROR: 2,
+ WARN: 3,
+ INFO: 4,
+ }.freeze
+
+ def self.warn msg
+ log :WARN, msg
+ end
+
+ def self.info msg
+ log :INFO, msg
+ end
+
+ def self.log level, msg
+ lv = LOG_LEVELS[level]
+ config_lv = LOG_LEVELS[CONFIG[:log_level] || :WARN]
+
+ if lv <= config_lv
+ if level == :WARN
+ # :WARN on debugger is general information
+ STDERR.puts "DEBUGGER: #{msg}"
+ else
+ STDERR.puts "DEBUGGER (#{level}): #{msg}"
+ end
end
end
end