rudebug 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +56 -0
- data/NEWS +6 -0
- data/README +31 -0
- data/Rakefile +112 -0
- data/bin/rudebug +4 -0
- data/lib/rudebug.glade +894 -0
- data/lib/rudebug.rb +13 -0
- data/lib/rudebug/connect.rb +203 -0
- data/lib/rudebug/connection.rb +3 -0
- data/lib/rudebug/connection/base.rb +109 -0
- data/lib/rudebug/connection/breakpoint.rb +109 -0
- data/lib/rudebug/connection/ruby-debug.rb +133 -0
- data/lib/rudebug/gtk-patch.rb +24 -0
- data/lib/rudebug/highlight.rb +129 -0
- data/lib/rudebug/main.rb +62 -0
- data/lib/rudebug/page.rb +75 -0
- data/lib/rudebug/page/browser.rb +291 -0
- data/lib/rudebug/page/code_file.rb +89 -0
- data/lib/rudebug/page/shell.rb +91 -0
- data/lib/rudebug/page/source_code.rb +54 -0
- data/setup.rb +1360 -0
- metadata +88 -0
data/lib/rudebug.rb
ADDED
@@ -0,0 +1,203 @@
|
|
1
|
+
require 'rudebug/connection'
|
2
|
+
|
3
|
+
class RuDebug
|
4
|
+
attr_reader :pages
|
5
|
+
|
6
|
+
def initialize_connect()
|
7
|
+
@connect_dlg = @glade["connect-dialog"]
|
8
|
+
@connectdlg_entry = @glade["connectdlg-server-entry"]
|
9
|
+
@connectdlg_type = @glade["connectdlg-type-combobox"]
|
10
|
+
@connectdlg_btn = @glade["connectdlg-button"]
|
11
|
+
|
12
|
+
load_connect_servers()
|
13
|
+
|
14
|
+
@connect_btn = @glade["connect-button"]
|
15
|
+
@disconnect_btn = @glade["disconnect-button"]
|
16
|
+
@continue_btn = @glade["continue-button"]
|
17
|
+
@step_into_btn = @glade["step-into-button"]
|
18
|
+
@step_over_btn = @glade["step-over-button"]
|
19
|
+
|
20
|
+
@connect_menuitem = @glade["connect-menuitem"]
|
21
|
+
@disconnect_menuitem = @glade["disconnect-menuitem"]
|
22
|
+
|
23
|
+
@connection_status = @glade["status-label"]
|
24
|
+
@connect_dlg.transient_for = @window
|
25
|
+
|
26
|
+
@connect_page = @notebook.get_nth_page(0)
|
27
|
+
@not_connected = @connection_status.text
|
28
|
+
@connection = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def load_connect_servers()
|
32
|
+
default_servers = [
|
33
|
+
"localhost:8989",
|
34
|
+
"druby://localhost:42531"
|
35
|
+
]
|
36
|
+
|
37
|
+
conf_servers = @config[:servers].to_ary rescue []
|
38
|
+
servers = (conf_servers + default_servers).uniq
|
39
|
+
|
40
|
+
servers.each do |server|
|
41
|
+
@connectdlg_entry.append_text(server)
|
42
|
+
end
|
43
|
+
|
44
|
+
@connectdlg_entry.active = 0
|
45
|
+
end
|
46
|
+
|
47
|
+
def save_connect_servers()
|
48
|
+
servers = []
|
49
|
+
@connectdlg_entry.model.each do |model, path, iter|
|
50
|
+
servers << iter[0]
|
51
|
+
end
|
52
|
+
|
53
|
+
@config[:servers] = servers
|
54
|
+
end
|
55
|
+
|
56
|
+
def on_connect_button_clicked()
|
57
|
+
@connect_dlg.show
|
58
|
+
@connectdlg_btn.grab_focus
|
59
|
+
end
|
60
|
+
alias :on_connect_activate :on_connect_button_clicked
|
61
|
+
|
62
|
+
def on_disconnect_button_clicked()
|
63
|
+
disconnect()
|
64
|
+
end
|
65
|
+
alias :on_disconnect_activate :on_disconnect_button_clicked
|
66
|
+
|
67
|
+
def on_connect_dialog_delete_event(dialog, event)
|
68
|
+
dialog.hide_on_delete
|
69
|
+
end
|
70
|
+
|
71
|
+
def on_connectdlg_server_entry_changed()
|
72
|
+
text = @connectdlg_entry.active_text
|
73
|
+
@connectdlg_type.active = text[/druby/i] ? 0 : 1
|
74
|
+
@connectdlg_btn.sensitive = text.size > 0
|
75
|
+
end
|
76
|
+
|
77
|
+
def on_connectdlg_server_entry_key_press_event(server_entry, event)
|
78
|
+
case event.keyval
|
79
|
+
when Gdk::Keyval::GDK_Return then
|
80
|
+
on_connectdlg_button_clicked()
|
81
|
+
else
|
82
|
+
return false
|
83
|
+
end
|
84
|
+
|
85
|
+
return true
|
86
|
+
end
|
87
|
+
|
88
|
+
def on_connectdlg_button_clicked()
|
89
|
+
text = @connectdlg_entry.active_text
|
90
|
+
@connect_dlg.hide
|
91
|
+
|
92
|
+
type_text = @connectdlg_type.active_text
|
93
|
+
|
94
|
+
con_type = case type_text
|
95
|
+
when "ruby-breakpoint" then Connection::Breakpoint
|
96
|
+
when "ruby-debug" then Connection::RubyDebug
|
97
|
+
end
|
98
|
+
|
99
|
+
unless text[/:\d/]
|
100
|
+
text += ":%d" % case type_text
|
101
|
+
when "ruby-breakpoint" then "wtf".to_i(36) # >:)
|
102
|
+
when "ruby-debug" then 8989
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
connect_to(text, con_type)
|
107
|
+
end
|
108
|
+
|
109
|
+
def connect_to(server, con_type)
|
110
|
+
@connection_status.text = @connect_text = "Connecting to %s..." % server
|
111
|
+
|
112
|
+
@connect_thread = Thread.new do
|
113
|
+
begin
|
114
|
+
@connection = con_type.new
|
115
|
+
@connection.on_session = method(:on_session)
|
116
|
+
@connection.on_disconnect = method(:on_disconnect)
|
117
|
+
@connection.connect(server)
|
118
|
+
rescue Exception => error
|
119
|
+
on_failure(error)
|
120
|
+
sleep 2
|
121
|
+
retry
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
update_connect_buttons()
|
126
|
+
end
|
127
|
+
|
128
|
+
def update_connect_buttons()
|
129
|
+
@connect_btn.sensitive = @connect_menuitem.sensitive = !@connection
|
130
|
+
@disconnect_btn.sensitive = @disconnect_menuitem.sensitive = @connection
|
131
|
+
end
|
132
|
+
|
133
|
+
def update_session_buttons()
|
134
|
+
has_page = current_page()
|
135
|
+
@continue_btn.sensitive = has_page
|
136
|
+
|
137
|
+
stepping = @connection.stepping_support?() if @connection
|
138
|
+
@step_into_btn.sensitive = @step_over_btn.sensitive = stepping && has_page
|
139
|
+
end
|
140
|
+
|
141
|
+
def disconnect()
|
142
|
+
connection = @connection
|
143
|
+
on_disconnect()
|
144
|
+
|
145
|
+
Thread.new do
|
146
|
+
connection.disconnect unless connection.nil?
|
147
|
+
|
148
|
+
unless @connect_thread.nil?
|
149
|
+
@connect_thread.kill
|
150
|
+
@connect_thread = nil
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def current_page()
|
156
|
+
active_page = @notebook.get_nth_page(@notebook.page)
|
157
|
+
title = @notebook.get_tab_label_text(active_page)
|
158
|
+
@pages[title]
|
159
|
+
end
|
160
|
+
|
161
|
+
def on_session(session)
|
162
|
+
@notebook.remove_page(0) # Remove connect page
|
163
|
+
@notebook.show_tabs = true
|
164
|
+
@pages[session.title] = Page.new(self, session)
|
165
|
+
update_session_buttons()
|
166
|
+
end
|
167
|
+
|
168
|
+
def on_disconnect()
|
169
|
+
@connection = nil
|
170
|
+
update_connect_buttons()
|
171
|
+
update_session_buttons()
|
172
|
+
|
173
|
+
@notebook.show_tabs = false
|
174
|
+
# Remove pages (session and connection)
|
175
|
+
# TODO: Only remove connection
|
176
|
+
@notebook.remove_page(0) until @notebook.n_pages == 0
|
177
|
+
|
178
|
+
@connection_status.text = @not_connected
|
179
|
+
@notebook.append_page(@connect_page)
|
180
|
+
end
|
181
|
+
|
182
|
+
def on_failure(error)
|
183
|
+
puts error, error.backtrace
|
184
|
+
|
185
|
+
@connection_status.text = [
|
186
|
+
@connect_text,
|
187
|
+
"Failed: %s" % error,
|
188
|
+
"Will reconnect..."
|
189
|
+
].join("\n\n")
|
190
|
+
end
|
191
|
+
|
192
|
+
def on_continue_button_clicked(btn)
|
193
|
+
btn.sensitive = false if current_page().continue().nil?
|
194
|
+
end
|
195
|
+
|
196
|
+
def on_step_into_button_clicked(btn)
|
197
|
+
btn.sensitive = false if current_page().step_into().nil?
|
198
|
+
end
|
199
|
+
|
200
|
+
def on_step_over_button_clicked(btn)
|
201
|
+
btn.sensitive = false if current_page().step_over().nil?
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
class Connection
|
2
|
+
class RemoteException < Exception
|
3
|
+
end
|
4
|
+
|
5
|
+
class Base
|
6
|
+
attr_accessor :on_session, :on_disconnect
|
7
|
+
|
8
|
+
Code = <<-END_CODE
|
9
|
+
$rudebug = Module.new do
|
10
|
+
extend self
|
11
|
+
|
12
|
+
def inspect()
|
13
|
+
"$rudebug"
|
14
|
+
end
|
15
|
+
alias :name :inspect
|
16
|
+
|
17
|
+
attr_accessor :selected
|
18
|
+
|
19
|
+
const_set(:Refs, [])
|
20
|
+
|
21
|
+
def add_ref(ref)
|
22
|
+
$rudebug::Refs << ref
|
23
|
+
return ref
|
24
|
+
end
|
25
|
+
|
26
|
+
const_set(:Pairs, Class.new {
|
27
|
+
def self.[](*pairs)
|
28
|
+
new(*pairs)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.to_s()
|
32
|
+
"$rudebug::Pairs"
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(*pairs)
|
36
|
+
@pairs = pairs
|
37
|
+
end
|
38
|
+
|
39
|
+
def inspect()
|
40
|
+
"{ %s }" % @pairs.map do |key, value|
|
41
|
+
[
|
42
|
+
key.is_a?(Numeric) ? key : key.to_sym.inspect,
|
43
|
+
$rudebug.inspect_obj(value)
|
44
|
+
].join(" => ")
|
45
|
+
end.join(", ")
|
46
|
+
end
|
47
|
+
|
48
|
+
def each(&block)
|
49
|
+
@pairs.each(&block)
|
50
|
+
end
|
51
|
+
|
52
|
+
def size()
|
53
|
+
@pairs.size
|
54
|
+
end
|
55
|
+
|
56
|
+
include Enumerable
|
57
|
+
})
|
58
|
+
|
59
|
+
def to_pairs(items, &block)
|
60
|
+
$rudebug::Pairs[*items.map { |item| [item, block.call(item)] }]
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_list(items)
|
64
|
+
to_pairs(0 .. items.size - 1) { |idx| items[idx] }
|
65
|
+
end
|
66
|
+
|
67
|
+
def inspect_obj(obj)
|
68
|
+
case obj
|
69
|
+
when UnboundMethod, Method then
|
70
|
+
inspect_method(obj)
|
71
|
+
else
|
72
|
+
#require 'pp'
|
73
|
+
#PP.pp(obj, "", 30).chomp
|
74
|
+
obj.inspect
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def inspect_method(method)
|
79
|
+
re = /^\x23<(?:Unbound)?Method: ([^(]+)(?:\\(([^)]+)\\))?\x23(.+?)>$/
|
80
|
+
if md = re.match(method.inspect) then
|
81
|
+
klass, diff_origin, name = *md.captures
|
82
|
+
origin = diff_origin || klass
|
83
|
+
argc = method.arity
|
84
|
+
more = argc < 0
|
85
|
+
argc = -argc if more
|
86
|
+
|
87
|
+
old_name = "a"
|
88
|
+
args = (1 .. argc).map do |idx|
|
89
|
+
arg_name = old_name.dup
|
90
|
+
old_name.succ!
|
91
|
+
arg_name
|
92
|
+
end
|
93
|
+
args[-1][0, 0] = "*" if more
|
94
|
+
|
95
|
+
"%s\x23%s(%s)" % [origin, name, args.join(", ")]
|
96
|
+
else
|
97
|
+
method.inspect
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
END_CODE
|
102
|
+
|
103
|
+
def self.load_rudebug_code(session)
|
104
|
+
session.eval(Code)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
eval(Connection::Base::Code)
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'rudebug/connection/base'
|
2
|
+
|
3
|
+
class Connection::Breakpoint < Connection::Base
|
4
|
+
def stepping_support?()
|
5
|
+
false
|
6
|
+
end
|
7
|
+
|
8
|
+
class Session
|
9
|
+
attr_reader :title
|
10
|
+
|
11
|
+
def initialize(workspace, title = nil)
|
12
|
+
@workspace = workspace
|
13
|
+
@title = title
|
14
|
+
@done = false
|
15
|
+
|
16
|
+
Connection::Base.load_rudebug_code(self)
|
17
|
+
end
|
18
|
+
|
19
|
+
def eval(code)
|
20
|
+
@workspace.evaluate(nil, code).inspect
|
21
|
+
rescue Exception => e
|
22
|
+
raise(Connection::RemoteException, e.inspect)
|
23
|
+
end
|
24
|
+
|
25
|
+
def current_file()
|
26
|
+
Kernel.eval(eval("bp.file rescue @__bp_file"))
|
27
|
+
end
|
28
|
+
|
29
|
+
def current_line()
|
30
|
+
Kernel.eval(eval("bp.line rescue @__bp_line")).to_i
|
31
|
+
end
|
32
|
+
|
33
|
+
def source_code(file)
|
34
|
+
Kernel.eval(eval("File.read(%p)" % file)).chomp
|
35
|
+
end
|
36
|
+
|
37
|
+
def continue()
|
38
|
+
@done = true
|
39
|
+
return nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def done?()
|
43
|
+
@done
|
44
|
+
end
|
45
|
+
|
46
|
+
# Safety
|
47
|
+
def step_into() end
|
48
|
+
def step_over() end
|
49
|
+
end
|
50
|
+
|
51
|
+
def connect(server)
|
52
|
+
require 'breakpoint'
|
53
|
+
require 'breakpoint/drb'
|
54
|
+
require 'drb'
|
55
|
+
|
56
|
+
DRb.start_service()
|
57
|
+
|
58
|
+
@service = DRbObject.new(nil, server)
|
59
|
+
@service.eval_handler = method(:eval_handler)
|
60
|
+
@service.handler = method(:breakpoint_handler)
|
61
|
+
|
62
|
+
@check_thread = Thread.new(
|
63
|
+
@service, @on_disconnect
|
64
|
+
) do |service, on_disconnect|
|
65
|
+
loop do
|
66
|
+
begin
|
67
|
+
service.ping
|
68
|
+
rescue DRb::DRbConnError => error
|
69
|
+
on_disconnect.call()
|
70
|
+
disconnect()
|
71
|
+
break
|
72
|
+
end
|
73
|
+
|
74
|
+
sleep(0.5)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def disconnect()
|
80
|
+
begin
|
81
|
+
@service.handler = nil
|
82
|
+
@service.eval_handler = nil
|
83
|
+
rescue Exception
|
84
|
+
end
|
85
|
+
|
86
|
+
@check_thread.kill if @check_thread
|
87
|
+
@check_thread = nil
|
88
|
+
|
89
|
+
DRb.stop_service()
|
90
|
+
@service = nil
|
91
|
+
end
|
92
|
+
|
93
|
+
def title_filter(message)
|
94
|
+
message[/"([^"]+)"/, 1] || message #"
|
95
|
+
end
|
96
|
+
|
97
|
+
def breakpoint_handler(workspace, message)
|
98
|
+
title = title_filter(message)
|
99
|
+
session = Session.new(workspace, title)
|
100
|
+
@on_session.call(session)
|
101
|
+
sleep 0.1 until session.done?
|
102
|
+
end
|
103
|
+
|
104
|
+
def eval_handler(code)
|
105
|
+
result = eval(code, TOPLEVEL_BINDING)
|
106
|
+
Breakpoint.undumpify(result)
|
107
|
+
return result
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'rudebug/connection/base'
|
2
|
+
|
3
|
+
class Connection::RubyDebug < Connection::Base
|
4
|
+
def stepping_support?()
|
5
|
+
true
|
6
|
+
end
|
7
|
+
|
8
|
+
class Session
|
9
|
+
attr_reader :title
|
10
|
+
|
11
|
+
def initialize(connection, location)
|
12
|
+
@connection = connection
|
13
|
+
@location = location
|
14
|
+
@title = location.join(":")
|
15
|
+
@done = false
|
16
|
+
|
17
|
+
Connection::Base.load_rudebug_code(self)
|
18
|
+
end
|
19
|
+
|
20
|
+
def execute(cmd)
|
21
|
+
result = @connection.execute(cmd)
|
22
|
+
result ? result.chomp : result
|
23
|
+
end
|
24
|
+
|
25
|
+
def eval(code)
|
26
|
+
cmd = %(
|
27
|
+
p begin;
|
28
|
+
[eval(%p, nil, *([
|
29
|
+
caller[0].split(":")[0],
|
30
|
+
caller[0].split(":")[1].to_i
|
31
|
+
] rescue [])).inspect, nil];
|
32
|
+
rescue Exception;
|
33
|
+
[nil, $!.inspect];
|
34
|
+
end
|
35
|
+
).strip.gsub(/\n\s*/, "")
|
36
|
+
result, error = *Kernel.eval(execute(cmd % [code, @location]))
|
37
|
+
|
38
|
+
raise(Connection::RemoteException, error) if error
|
39
|
+
return result
|
40
|
+
end
|
41
|
+
|
42
|
+
def current_file()
|
43
|
+
@location[0]
|
44
|
+
end
|
45
|
+
|
46
|
+
def current_line()
|
47
|
+
@location[1].to_i
|
48
|
+
end
|
49
|
+
|
50
|
+
def source_code(file)
|
51
|
+
Kernel.eval(eval("File.read(%p)" % file)).chomp
|
52
|
+
end
|
53
|
+
|
54
|
+
def generic_step(cmd)
|
55
|
+
result = execute(cmd)
|
56
|
+
|
57
|
+
if result then
|
58
|
+
@connection.location?(result)
|
59
|
+
else
|
60
|
+
@connection.on_disconnect.call()
|
61
|
+
@connection.disconnect()
|
62
|
+
return nil
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def continue()
|
67
|
+
generic_step("cont")
|
68
|
+
end
|
69
|
+
|
70
|
+
def step_into()
|
71
|
+
generic_step("step")
|
72
|
+
end
|
73
|
+
|
74
|
+
def step_over()
|
75
|
+
generic_step("next")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def connect(server)
|
80
|
+
require 'ruby-debug'
|
81
|
+
require 'socket'
|
82
|
+
require 'thread'
|
83
|
+
|
84
|
+
host, port = */^(.+):(\d+)$/.match(server).captures
|
85
|
+
port = port.to_i
|
86
|
+
|
87
|
+
@socket = TCPSocket.new(host, port)
|
88
|
+
line = @socket.gets
|
89
|
+
location = location?(line)
|
90
|
+
|
91
|
+
result = @socket.gets.strip rescue nil
|
92
|
+
if !prompt?(result) then
|
93
|
+
raise "Not a ruby-debug server"
|
94
|
+
end
|
95
|
+
|
96
|
+
@mutex = Mutex.new
|
97
|
+
|
98
|
+
session = Session.new(self, location)
|
99
|
+
@on_session.call(session)
|
100
|
+
end
|
101
|
+
|
102
|
+
def prompt?(line)
|
103
|
+
line[/^PROMPT /] rescue nil
|
104
|
+
end
|
105
|
+
|
106
|
+
def location?(line)
|
107
|
+
if md = /^(.+?):(\d+): /.match(line) then
|
108
|
+
file, line = *md.captures
|
109
|
+
[file, line.to_i]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def execute(cmd)
|
114
|
+
@mutex.synchronize do
|
115
|
+
@socket.puts(cmd)
|
116
|
+
result = @socket.gets
|
117
|
+
prompt = @socket.gets
|
118
|
+
|
119
|
+
#p result
|
120
|
+
#p [:prompt, prompt]
|
121
|
+
return result
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def disconnect()
|
126
|
+
begin
|
127
|
+
@socket.close
|
128
|
+
rescue Exception
|
129
|
+
end
|
130
|
+
|
131
|
+
@socket = @mutex = nil
|
132
|
+
end
|
133
|
+
end
|