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