rudebug 0.3.0
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/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
|