summaryrefslogtreecommitdiff
path: root/lib/irb/command/debug.rb
blob: bdf91766bcc335bd56f6e36afd6c268c96e6e4c7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
require_relative "../debug"

module IRB
  # :stopdoc:

  module Command
    class Debug < Base
      category "Debugging"
      description "Start the debugger of debug.gem."

      BINDING_IRB_FRAME_REGEXPS = [
        '<internal:prelude>',
        binding.method(:irb).source_location.first,
      ].map { |file| /\A#{Regexp.escape(file)}:\d+:in (`|'Binding#)irb'\z/ }

      def execute(pre_cmds: nil, do_cmds: nil)
        if irb_context.with_debugger
          # If IRB is already running with a debug session, throw the command and IRB.debug_readline will pass it to the debugger.
          if cmd = pre_cmds || do_cmds
            throw :IRB_EXIT, cmd
          else
            puts "IRB is already running with a debug session."
            return
          end
        else
          # If IRB is not running with a debug session yet, then:
          # 1. Check if the debugging command is run from a `binding.irb` call.
          # 2. If so, try setting up the debug gem.
          # 3. Insert a debug breakpoint at `Irb#debug_break` with the intended command.
          # 4. Exit the current Irb#run call via `throw :IRB_EXIT`.
          # 5. `Irb#debug_break` will be called and trigger the breakpoint, which will run the intended command.
          unless binding_irb?
            puts "Debugging commands are only available when IRB is started with binding.irb"
            return
          end

          if IRB.respond_to?(:JobManager)
            warn "Can't start the debugger when IRB is running in a multi-IRB session."
            return
          end

          unless IRB::Debug.setup(irb_context.irb)
            puts <<~MSG
              You need to install the debug gem before using this command.
              If you use `bundle exec`, please add `gem "debug"` into your Gemfile.
            MSG
            return
          end

          IRB::Debug.insert_debug_break(pre_cmds: pre_cmds, do_cmds: do_cmds)

          # exit current Irb#run call
          throw :IRB_EXIT
        end
      end

      private

      def binding_irb?
        caller.any? do |frame|
          BINDING_IRB_FRAME_REGEXPS.any? do |regexp|
            frame.match?(regexp)
          end
        end
      end
    end

    class DebugCommand < Debug
      def self.category
        "Debugging"
      end

      def self.description
        command_name = self.name.split("::").last.downcase
        "Start the debugger of debug.gem and run its `#{command_name}` command."
      end
    end
  end
end