ruby-debug-ide 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +0 -0
- data/bin/rdebug-ide +74 -0
- data/lib/ruby-debug.rb +128 -0
- data/lib/ruby-debug/command.rb +114 -0
- data/lib/ruby-debug/commands/breakpoints.rb +126 -0
- data/lib/ruby-debug/commands/catchpoint.rb +40 -0
- data/lib/ruby-debug/commands/control.rb +127 -0
- data/lib/ruby-debug/commands/eval.rb +64 -0
- data/lib/ruby-debug/commands/frame.rb +161 -0
- data/lib/ruby-debug/commands/inspect.rb +24 -0
- data/lib/ruby-debug/commands/load.rb +18 -0
- data/lib/ruby-debug/commands/stepping.rb +105 -0
- data/lib/ruby-debug/commands/threads.rb +153 -0
- data/lib/ruby-debug/commands/variables.rb +123 -0
- data/lib/ruby-debug/event_processor.rb +45 -0
- data/lib/ruby-debug/interface.rb +118 -0
- data/lib/ruby-debug/printers.rb +2 -0
- data/lib/ruby-debug/processor.rb +155 -0
- data/lib/ruby-debug/xml_printer.rb +237 -0
- metadata +74 -0
@@ -0,0 +1,153 @@
|
|
1
|
+
module Debugger
|
2
|
+
class ThreadListCommand < Command # :nodoc:
|
3
|
+
self.control = true
|
4
|
+
def regexp
|
5
|
+
/^\s*th(?:read)?\s+l(?:ist)?\s*$/
|
6
|
+
end
|
7
|
+
|
8
|
+
def execute
|
9
|
+
contexts = Debugger.contexts.sort_by{|c| c.thnum}
|
10
|
+
print_contexts(contexts)
|
11
|
+
end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def help_command
|
15
|
+
'thread'
|
16
|
+
end
|
17
|
+
|
18
|
+
def help(cmd)
|
19
|
+
%{
|
20
|
+
th[read] l[ist]\t\t\tlist all threads
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class ThreadSwitchCommand < Command # :nodoc:
|
27
|
+
self.control = true
|
28
|
+
self.need_context = true
|
29
|
+
|
30
|
+
def regexp
|
31
|
+
/^\s*th(?:read)?\s+(?:sw(?:itch)?\s+)?(\d+)\s*$/
|
32
|
+
end
|
33
|
+
|
34
|
+
def execute
|
35
|
+
c = get_context(@match[1].to_i)
|
36
|
+
case
|
37
|
+
when c == @state.context
|
38
|
+
print_msg "It's the current thread."
|
39
|
+
when c.ignored?
|
40
|
+
print_msg "Can't switch to the debugger thread."
|
41
|
+
else
|
42
|
+
print_context(c)
|
43
|
+
c.stop_next = 1
|
44
|
+
c.thread.run
|
45
|
+
@state.proceed
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class << self
|
50
|
+
def help_command
|
51
|
+
'thread'
|
52
|
+
end
|
53
|
+
|
54
|
+
def help(cmd)
|
55
|
+
%{
|
56
|
+
th[read] [sw[itch]] <nnn>\tswitch thread context to nnn
|
57
|
+
}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class ThreadStopCommand < Command # :nodoc:
|
63
|
+
self.control = true
|
64
|
+
self.need_context = true
|
65
|
+
|
66
|
+
def regexp
|
67
|
+
/^\s*th(?:read)?\s+stop\s+(\d+)\s*$/
|
68
|
+
end
|
69
|
+
|
70
|
+
def execute
|
71
|
+
c = get_context(@match[1].to_i)
|
72
|
+
case
|
73
|
+
when c == @state.context
|
74
|
+
print_msg "It's the current thread."
|
75
|
+
when c.ignored?
|
76
|
+
print_msg "Can't stop the debugger thread."
|
77
|
+
else
|
78
|
+
c.suspend
|
79
|
+
print_context(c)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class << self
|
84
|
+
def help_command
|
85
|
+
'thread'
|
86
|
+
end
|
87
|
+
|
88
|
+
def help(cmd)
|
89
|
+
%{
|
90
|
+
th[read] stop <nnn>\t\tstop thread nnn
|
91
|
+
}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class ThreadCurrentCommand < Command # :nodoc:
|
97
|
+
self.need_context = true
|
98
|
+
|
99
|
+
def regexp
|
100
|
+
/^\s*th(?:read)?\s+c(?:ur(?:rent)?)?\s*$/
|
101
|
+
end
|
102
|
+
|
103
|
+
def execute
|
104
|
+
print_context(@state.context)
|
105
|
+
end
|
106
|
+
|
107
|
+
class << self
|
108
|
+
def help_command
|
109
|
+
'thread'
|
110
|
+
end
|
111
|
+
|
112
|
+
def help(cmd)
|
113
|
+
%{
|
114
|
+
th[read] c[ur[rent]]\t\tshow current thread
|
115
|
+
}
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class ThreadResumeCommand < Command # :nodoc:
|
121
|
+
self.control = true
|
122
|
+
self.need_context = true
|
123
|
+
|
124
|
+
def regexp
|
125
|
+
/^\s*th(?:read)?\s+resume\s+(\d+)\s*$/
|
126
|
+
end
|
127
|
+
|
128
|
+
def execute
|
129
|
+
c = get_context(@match[1].to_i)
|
130
|
+
case
|
131
|
+
when c == @state.context
|
132
|
+
print_msg "It's the current thread."
|
133
|
+
when c.ignored?
|
134
|
+
print_msg "Can't resume the debugger thread."
|
135
|
+
else
|
136
|
+
c.resume
|
137
|
+
print_context(c)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
class << self
|
142
|
+
def help_command
|
143
|
+
'thread'
|
144
|
+
end
|
145
|
+
|
146
|
+
def help(cmd)
|
147
|
+
%{
|
148
|
+
th[read] resume <nnn>\t\tresume thread nnn
|
149
|
+
}
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module Debugger
|
2
|
+
class VarConstantCommand < Command # :nodoc:
|
3
|
+
def regexp
|
4
|
+
/^\s*v(?:ar)?\s+c(?:onst(?:ant)?)?\s+/
|
5
|
+
end
|
6
|
+
|
7
|
+
def execute
|
8
|
+
obj = debug_eval(@match.post_match)
|
9
|
+
unless obj.kind_of? Module
|
10
|
+
print_msg "Should be Class/Module: %s", @match.post_match
|
11
|
+
else
|
12
|
+
print_variables(obj.constants, "constant") do |var|
|
13
|
+
obj.const_get(var)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
def help_command
|
20
|
+
'var'
|
21
|
+
end
|
22
|
+
|
23
|
+
def help(cmd)
|
24
|
+
%{
|
25
|
+
v[ar] c[onst] <object>\t\tshow constants of object
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class VarGlobalCommand < Command # :nodoc:
|
32
|
+
def regexp
|
33
|
+
/^\s*v(?:ar)?\s+g(?:lobal)?\s*$/
|
34
|
+
end
|
35
|
+
|
36
|
+
def execute
|
37
|
+
print_variables(global_variables, 'global') do |var|
|
38
|
+
debug_eval(var)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class << self
|
43
|
+
def help_command
|
44
|
+
'var'
|
45
|
+
end
|
46
|
+
|
47
|
+
def help(cmd)
|
48
|
+
%{
|
49
|
+
v[ar] g[lobal]\t\t\tshow global variables
|
50
|
+
}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class VarInstanceCommand < Command # :nodoc:
|
56
|
+
def regexp
|
57
|
+
# id will be read as first match, name as post match
|
58
|
+
/^\s*v(?:ar)?\s+i(?:nstance)?\s+((?:[\\+-]0x)[\dabcdef]+)?/
|
59
|
+
end
|
60
|
+
|
61
|
+
def execute
|
62
|
+
if (@match[1])
|
63
|
+
obj = ObjectSpace._id2ref(@match[1].hex) rescue nil
|
64
|
+
unless obj
|
65
|
+
# TODO: ensure that empty variables frame will be printed
|
66
|
+
@printer.print_msg("Unknown object id : %s", @match[1])
|
67
|
+
end
|
68
|
+
else
|
69
|
+
obj = debug_eval(@match.post_match)
|
70
|
+
end
|
71
|
+
return unless obj
|
72
|
+
if (obj.class.name == "Array") then
|
73
|
+
print_array(obj)
|
74
|
+
elsif (obj.class.name == "Hash") then
|
75
|
+
print_hash(obj)
|
76
|
+
else
|
77
|
+
b = obj.instance_eval{binding()}
|
78
|
+
print_variables(obj.instance_variables, 'instance') do |var|
|
79
|
+
debug_eval(var, b)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class << self
|
85
|
+
def help_command
|
86
|
+
'var'
|
87
|
+
end
|
88
|
+
|
89
|
+
def help(cmd)
|
90
|
+
%{
|
91
|
+
v[ar] i[nstance] <object>\tshow instance variables of object, object can be given by its id or an expression
|
92
|
+
}
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class VarLocalCommand < Command # :nodoc:
|
98
|
+
def regexp
|
99
|
+
/^\s*v(?:ar)?\s+l(?:ocal)?\s*$/
|
100
|
+
end
|
101
|
+
|
102
|
+
def execute
|
103
|
+
locals = @state.context.frame_locals(@state.frame_pos)
|
104
|
+
_self = @state.context.frame_self(@state.frame_pos)
|
105
|
+
locals['self'] = _self unless _self.to_s == "main"
|
106
|
+
print_variables(locals.keys, 'local') do |var|
|
107
|
+
locals[var]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class << self
|
112
|
+
def help_command
|
113
|
+
'var'
|
114
|
+
end
|
115
|
+
|
116
|
+
def help(cmd)
|
117
|
+
%{
|
118
|
+
v[ar] l[ocal]\t\t\tshow local variables
|
119
|
+
}
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'ruby-debug/xml_printer'
|
2
|
+
module Debugger
|
3
|
+
|
4
|
+
class EventProcessor
|
5
|
+
|
6
|
+
attr_accessor :line, :file, :context
|
7
|
+
|
8
|
+
def initialize(interface)
|
9
|
+
@printer = XmlPrinter.new(interface)
|
10
|
+
@line = nil
|
11
|
+
@file = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def at_breakpoint(context, breakpoint)
|
15
|
+
n = Debugger.breakpoints.index(breakpoint) + 1
|
16
|
+
@printer.print_breakpoint n, breakpoint
|
17
|
+
end
|
18
|
+
|
19
|
+
def at_catchpoint(context, excpt)
|
20
|
+
@printer.print_catchpoint(excpt)
|
21
|
+
end
|
22
|
+
|
23
|
+
def at_tracing(context, file, line)
|
24
|
+
@printer.print_trace(context, file, line)
|
25
|
+
end
|
26
|
+
|
27
|
+
def at_line(context, file, line)
|
28
|
+
@printer.print_at_line(file, line) if context.nil? || context.stop_reason == :step
|
29
|
+
@line=line
|
30
|
+
@file =file
|
31
|
+
@context = context
|
32
|
+
@printer.print_debug("Stopping Thread %s", context.thread.to_s)
|
33
|
+
@printer.print_debug("Threads equal: %s", Thread.current == context.thread)
|
34
|
+
Thread.stop
|
35
|
+
@printer.print_debug("Resumed Thread %s", context.thread.to_s)
|
36
|
+
@line=nil
|
37
|
+
@file = nil
|
38
|
+
@context = nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def at_line?
|
42
|
+
@line
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module Debugger
|
2
|
+
class LocalInterface # :nodoc:
|
3
|
+
def read_command(prompt)
|
4
|
+
readline(prompt, true)
|
5
|
+
end
|
6
|
+
|
7
|
+
def confirm(prompt)
|
8
|
+
readline(prompt, false)
|
9
|
+
end
|
10
|
+
|
11
|
+
def print(*args)
|
12
|
+
STDOUT.printf(*args)
|
13
|
+
end
|
14
|
+
|
15
|
+
def close
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
begin
|
21
|
+
require 'readline'
|
22
|
+
class << Debugger
|
23
|
+
FILE_HISTORY = ".rdebug_hist"
|
24
|
+
save_file = File.join(ENV["HOME"]||ENV["HOMEPATH"], FILE_HISTORY)
|
25
|
+
open(save_file, 'r') do |file|
|
26
|
+
file.each do |line|
|
27
|
+
line.chomp!
|
28
|
+
Readline::HISTORY << line
|
29
|
+
end
|
30
|
+
end if File.exists?(save_file)
|
31
|
+
|
32
|
+
define_method(:save_history) do
|
33
|
+
open(save_file, 'w') do |file|
|
34
|
+
Readline::HISTORY.to_a.last(500).each do |line|
|
35
|
+
file.puts line unless line.strip.empty?
|
36
|
+
end
|
37
|
+
end rescue nil
|
38
|
+
end
|
39
|
+
public :save_history
|
40
|
+
end
|
41
|
+
Debugger.debug_at_exit { Debugger.save_history }
|
42
|
+
|
43
|
+
def readline(prompt, hist)
|
44
|
+
Readline::readline(prompt, hist)
|
45
|
+
end
|
46
|
+
rescue LoadError
|
47
|
+
def readline(prompt, hist)
|
48
|
+
STDOUT.print prompt
|
49
|
+
STDOUT.flush
|
50
|
+
line = STDIN.gets
|
51
|
+
exit unless line
|
52
|
+
line.chomp!
|
53
|
+
line
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class RemoteInterface # :nodoc:
|
59
|
+
def initialize(socket)
|
60
|
+
@socket = socket
|
61
|
+
end
|
62
|
+
|
63
|
+
def read_command(prompt)
|
64
|
+
send_command "PROMPT #{prompt}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def confirm(prompt)
|
68
|
+
send_command "CONFIRM #{prompt}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def print(*args)
|
72
|
+
@socket.printf(*args)
|
73
|
+
end
|
74
|
+
|
75
|
+
def close
|
76
|
+
@socket.close
|
77
|
+
rescue Exception
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def send_command(msg)
|
83
|
+
@socket.puts msg
|
84
|
+
result = @socket.gets
|
85
|
+
raise IOError unless result
|
86
|
+
result.chomp
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class ScriptInterface # :nodoc:
|
91
|
+
def initialize(file, out)
|
92
|
+
@file = file.respond_to?(:gets) ? file : open(file)
|
93
|
+
@out = out
|
94
|
+
end
|
95
|
+
|
96
|
+
def read_command(prompt)
|
97
|
+
while result = @file.gets
|
98
|
+
next if result =~ /^\s*#/
|
99
|
+
next if result.strip.empty?
|
100
|
+
break
|
101
|
+
end
|
102
|
+
raise IOError unless result
|
103
|
+
result
|
104
|
+
end
|
105
|
+
|
106
|
+
def confirm(prompt)
|
107
|
+
'y'
|
108
|
+
end
|
109
|
+
|
110
|
+
def print(*args)
|
111
|
+
@out.print(*args)
|
112
|
+
end
|
113
|
+
|
114
|
+
def close
|
115
|
+
@file.close
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'ruby-debug/interface'
|
2
|
+
require 'ruby-debug/command'
|
3
|
+
|
4
|
+
module Debugger
|
5
|
+
|
6
|
+
class ControlCommandProcessor # :nodoc:
|
7
|
+
def initialize(interface)
|
8
|
+
@interface = interface
|
9
|
+
@printer = XmlPrinter.new(@interface)
|
10
|
+
end
|
11
|
+
|
12
|
+
def print(*args)
|
13
|
+
@interface.print(*args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def process_commands
|
17
|
+
@printer.print_debug("Starting command read loop")
|
18
|
+
control_cmds = Command.commands.select{|cmd| cmd.control }
|
19
|
+
state = ControlState.new(@interface, control_cmds)
|
20
|
+
commands = control_cmds.map{|cmd| cmd.new(state, @printer) }
|
21
|
+
|
22
|
+
while input = @interface.read_command("")
|
23
|
+
# escape % since print_debug might use printf
|
24
|
+
@printer.print_debug "Processing: #{input.gsub('%', '%%')}"
|
25
|
+
catch(:debug_error) do
|
26
|
+
if cmd = commands.find{|c| c.match(input) }
|
27
|
+
cmd.execute
|
28
|
+
else
|
29
|
+
process_context_commands(input)
|
30
|
+
#@printer.print_msg "Unknown command"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
rescue IOError, Errno::EPIPE
|
35
|
+
rescue Exception
|
36
|
+
@printer.print_error "INTERNAL ERROR!!! #{$!}\n" rescue nil
|
37
|
+
@printer.print_error $!.backtrace.map{|l| "\t#{l}"}.join("\n") rescue nil
|
38
|
+
ensure
|
39
|
+
@interface.close
|
40
|
+
end
|
41
|
+
|
42
|
+
def process_context_commands(input)
|
43
|
+
unless Debugger.processor.at_line?
|
44
|
+
@printer.print_error "There is no thread suspended at the time and therefore no context to execute '#{input.gsub('%', '%%')}'"
|
45
|
+
return
|
46
|
+
end
|
47
|
+
context = Debugger.processor.context
|
48
|
+
file = Debugger.processor.file
|
49
|
+
line = Debugger.processor.line
|
50
|
+
event_cmds = Command.commands.select{|cmd| cmd.event }
|
51
|
+
state = State.new do |s|
|
52
|
+
s.context = context
|
53
|
+
s.file = file
|
54
|
+
s.line = line
|
55
|
+
s.binding = context.frame_binding(0)
|
56
|
+
s.display = display
|
57
|
+
s.interface = @interface
|
58
|
+
s.commands = event_cmds
|
59
|
+
end
|
60
|
+
commands = event_cmds.map{|cmd| cmd.new(state, @printer) }
|
61
|
+
catch(:debug_error) do
|
62
|
+
|
63
|
+
splitter[input].each do |input|
|
64
|
+
# escape % since print_debug might use printf
|
65
|
+
@printer.print_debug "Processing context: #{input.gsub('%', '%%')}"
|
66
|
+
if cmd = commands.find{ |c| c.match(input) }
|
67
|
+
if context.dead? && cmd.class.need_context
|
68
|
+
@printer.print_msg "Command is unavailable\n"
|
69
|
+
else
|
70
|
+
cmd.execute
|
71
|
+
end
|
72
|
+
else
|
73
|
+
@printer.print_msg "Unknown command: #{input}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context.thread.run if state.proceed?
|
79
|
+
end
|
80
|
+
|
81
|
+
def splitter
|
82
|
+
return lambda do |str|
|
83
|
+
str.split(/;/).inject([]) do |m, v|
|
84
|
+
if m.empty?
|
85
|
+
m << v
|
86
|
+
else
|
87
|
+
if m.last[-1] == ?\\
|
88
|
+
m.last[-1,1] = ''
|
89
|
+
m.last << ';' << v
|
90
|
+
else
|
91
|
+
m << v
|
92
|
+
end
|
93
|
+
end
|
94
|
+
m
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
class State # :nodoc:
|
100
|
+
attr_accessor :context, :file, :line, :binding
|
101
|
+
attr_accessor :frame_pos, :previous_line, :display
|
102
|
+
attr_accessor :interface, :commands
|
103
|
+
|
104
|
+
def initialize
|
105
|
+
@frame_pos = 0
|
106
|
+
@previous_line = nil
|
107
|
+
@proceed = false
|
108
|
+
yield self
|
109
|
+
end
|
110
|
+
|
111
|
+
def print(*args)
|
112
|
+
@interface.print(*args)
|
113
|
+
end
|
114
|
+
|
115
|
+
def confirm(*args)
|
116
|
+
@interface.confirm(*args)
|
117
|
+
end
|
118
|
+
|
119
|
+
def proceed?
|
120
|
+
@proceed
|
121
|
+
end
|
122
|
+
|
123
|
+
def proceed
|
124
|
+
@proceed = true
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class ControlState # :nodoc:
|
129
|
+
attr_reader :commands
|
130
|
+
def initialize(interface, commands)
|
131
|
+
@interface = interface
|
132
|
+
@commands = commands
|
133
|
+
end
|
134
|
+
|
135
|
+
def proceed
|
136
|
+
end
|
137
|
+
|
138
|
+
def print(*args)
|
139
|
+
@interface.print(*args)
|
140
|
+
end
|
141
|
+
|
142
|
+
def confirm(*args)
|
143
|
+
'y'
|
144
|
+
end
|
145
|
+
|
146
|
+
def context
|
147
|
+
nil
|
148
|
+
end
|
149
|
+
|
150
|
+
def file
|
151
|
+
print "ERROR: No filename given.\n"
|
152
|
+
throw :debug_error
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|