rudebug 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,24 @@
1
+ # This files contains fixes and enhancements to the GTK library
2
+
3
+ class GladeXML # :nodoc:
4
+ # There's a bug in GladeXML: It tries to guard all elements that were loaded
5
+ # from a glade file against the GC by holding a reference so they won't go
6
+ # away. However, it can try to reference objects which weren't loaded when we
7
+ # supply a root argument. This ought to protect against that case. -- flgr
8
+ alias :guard_source_from_gc_without_nil_check :guard_source_from_gc
9
+ def guard_source_from_gc(source)
10
+ guard_source_from_gc_without_nil_check(source) unless source.nil?
11
+ end
12
+ end
13
+
14
+ class Gtk::Container # :nodoc:
15
+ include Enumerable
16
+ end
17
+
18
+ class Gtk::ListStore # :nodoc:
19
+ include Enumerable
20
+
21
+ def size()
22
+ self.to_a().size()
23
+ end
24
+ end
@@ -0,0 +1,129 @@
1
+ begin
2
+ require 'syntax/convertors/abstract'
3
+
4
+ module Syntax
5
+ module Convertors
6
+ class GTKTextBuffer < Abstract
7
+ def self.color(red, green, blue)
8
+ Gdk::Color.new(red * 256, green * 256, blue * 256)
9
+ end
10
+
11
+ constant =
12
+ Styles = {
13
+ "number" => {
14
+ :foreground_gdk => color(0, 99, 0),
15
+ :background_gdk => color(222, 229, 157)
16
+ },
17
+ "string" => {
18
+ :priority => 0,
19
+ :foreground_gdk => color(153, 99, 112),
20
+ :background_gdk => color(255, 239, 216)
21
+ },
22
+ "regex" => {
23
+ :foreground_gdk => color(99, 153, 112),
24
+ :background_gdk => color(239, 255, 216)
25
+ },
26
+ "constant" => {
27
+ :foreground_gdk => color(58, 82, 120)
28
+ },
29
+ "method" => {
30
+ :foreground_gdk => color(127, 0, 127),
31
+ },
32
+ "attribute" => {
33
+ :foreground_gdk => color(153, 29, 170)
34
+ },
35
+ "symbol" => {
36
+ :priority => 0,
37
+ :foreground_gdk => color(152, 159, 154),
38
+ :background_gdk => color(243, 245, 244)
39
+ },
40
+ "keyword" => {
41
+ :foreground_gdk => color(16, 125, 202)
42
+ },
43
+ "escape" => {
44
+ :foreground_gdk => color(255, 255, 255),
45
+ :background_gdk => color(172, 115, 77)
46
+ },
47
+ "expr" => {
48
+ :foreground_gdk => color(188, 16, 2),
49
+ :background_gdk => color(255, 239, 216)
50
+ },
51
+ "punct" => {
52
+ :foreground_gdk => color(202, 0, 34)
53
+ },
54
+ "comment" => {
55
+ :foreground_gdk => color(153, 155, 154)
56
+ },
57
+ "ident" => {},
58
+ "expr" => {}
59
+ }
60
+ Styles["module"] = Styles["class"] = Styles["constant"]
61
+ Styles["global"] = Styles["punct"]
62
+ Styles["char"] = Styles["number"]
63
+
64
+ def style_for(tag)
65
+ Styles[tag.to_s]
66
+ end
67
+
68
+ def convert(code, buffer)
69
+ regions = []
70
+ region_tags = ["default"]
71
+
72
+ @tokenizer.tokenize(code) do |tok|
73
+ value = tok.to_s
74
+ group = tok.group.to_s
75
+
76
+ case tok.instruction
77
+ when :region_close then
78
+ region_tags.pop
79
+
80
+ when :region_open then
81
+ region_tags << group
82
+ end
83
+
84
+ unless value.empty?
85
+ tags = (region_tags + [group]) - ["normal"]
86
+ tags.uniq!
87
+
88
+ tags.reject! do |tag_name|
89
+ tag = buffer.tag_table.lookup(tag_name)
90
+ style = style_for(tag_name)
91
+ is_unknown = tag.nil? and style.nil?
92
+
93
+ if tag.nil? and !style.nil? then
94
+ priority = style.delete(:priority)
95
+ buffer.create_tag(tag_name, style)
96
+ if priority then
97
+ buffer.tag_table.lookup(tag_name).priority = priority
98
+ end
99
+ is_unknown = false
100
+ end
101
+
102
+ puts "Unknown tag %p" % tag_name if is_unknown
103
+ is_unknown
104
+ end
105
+
106
+ buffer.insert(buffer.end_iter, value, *tags.reverse)
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ def self.gtk_highlight(code, buffer)
114
+ @convertor ||= Syntax::Convertors::GTKTextBuffer.for_syntax "ruby"
115
+ @convertor.tokenizer.set(:expressions => :highlight)
116
+ @convertor.convert(code, buffer)
117
+ end
118
+ end
119
+ rescue LoadError => error
120
+ module Syntax
121
+ module Convertors
122
+ class GTKTextBuffer
123
+ def self.gtk_highlight(code, buffer)
124
+ buffer.insert(buffer.end_iter, code)
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,62 @@
1
+ require 'rudebug/gtk-patch'
2
+
3
+ class RuDebug
4
+ attr_reader :glade_file, :window, :notebook
5
+
6
+ def initialize(glade_file)
7
+ load_config()
8
+
9
+ @glade_file = glade_file
10
+ @glade = GladeXML.new(glade_file) do |handler|
11
+ method(handler) rescue lambda { |*args| p [handler, *args] }
12
+ end
13
+
14
+ @window = @glade["main-window"]
15
+ @notebook = @glade["session-notebook"]
16
+ @notebook.homogeneous = false
17
+ @notebook.remove_page(0)
18
+ @notebook.show_tabs = false
19
+ @pages = {}
20
+
21
+ initialize_connect()
22
+
23
+ @window.show
24
+ end
25
+
26
+ def config_path()
27
+ base_path = ENV["APPDATA"] || File.expand_path("~")
28
+ File.join(base_path, ".rudebug_conf")
29
+ end
30
+
31
+ def load_config()
32
+ @config = begin
33
+ YAML.load(File.read(config_path)).to_hash
34
+ rescue Exception
35
+ {}
36
+ end
37
+ end
38
+
39
+ def save_config()
40
+ save_connect_servers()
41
+
42
+ File.open(config_path, "w") do |file|
43
+ file.puts(@config.to_yaml)
44
+ end
45
+ rescue Exception
46
+ # Ignored
47
+ end
48
+
49
+ def on_main_window_destroy()
50
+ save_config()
51
+ Gtk.main_quit()
52
+ end
53
+ alias :on_quit_activate :on_main_window_destroy
54
+ end
55
+
56
+ require 'rudebug/page'
57
+ require 'rudebug/connect'
58
+ require 'rudebug/highlight'
59
+
60
+ Gtk.init
61
+ rudebug = RuDebug.new(File.join(DATA_PATH, "rudebug.glade"))
62
+ Gtk.main
@@ -0,0 +1,75 @@
1
+ class RuDebug::Page
2
+ attr_reader :session, :glade_file
3
+
4
+ def initialize(parent, session)
5
+ @parent = parent
6
+ @session = session
7
+ @glade_file = parent.glade_file
8
+
9
+ @glade = GladeXML.new(@glade_file, "session-hpaned") do |handler|
10
+ method(handler) rescue lambda { |*args| p [handler, *args] }
11
+ end
12
+
13
+ @page = @glade["session-hpaned"]
14
+
15
+ initialize_browser()
16
+ initialize_source_code()
17
+ initialize_shell()
18
+
19
+ load_browser()
20
+ load_current_code()
21
+
22
+ label = Gtk::Label.new(session.title)
23
+ @parent.notebook.prepend_page(@page, label)
24
+ end
25
+
26
+ def create_default_tag(buffer, size = nil, more = {})
27
+ size ||= 10
28
+ options = { "font" => "Andale Mono %d" % size }.merge(more)
29
+ buffer.create_tag("default", options)
30
+ end
31
+
32
+ def generic_step(method)
33
+ result = @session.__send__(method)
34
+
35
+ if result.nil? then
36
+ close()
37
+ else
38
+ update_location(*result)
39
+ load_browser()
40
+ title = result.join(":") # TODO: Query from server?
41
+ update_title(title)
42
+ end
43
+
44
+ return result
45
+ end
46
+
47
+ def update_title(title)
48
+ @parent.notebook.set_tab_label_text(@page, title)
49
+ @parent.notebook.set_menu_label_text(@page, title)
50
+ @parent.pages[title] = self
51
+ end
52
+
53
+ def continue()
54
+ generic_step(:continue)
55
+ end
56
+
57
+ def step_into()
58
+ generic_step(:step_into)
59
+ end
60
+
61
+ def step_over()
62
+ generic_step(:step_over)
63
+ end
64
+
65
+ def close()
66
+ page_num = @parent.notebook.page_num(@page)
67
+ if page_num != -1 then
68
+ @parent.notebook.remove_page(page_num)
69
+ end
70
+ end
71
+ end
72
+
73
+ require 'rudebug/page/shell'
74
+ require 'rudebug/page/source_code'
75
+ require 'rudebug/page/browser'
@@ -0,0 +1,291 @@
1
+ # This is all very complex because we can only send and receive strings via the
2
+ # network. (ruby-breakpoint can return DRbObject proxies which we could in
3
+ # theory work on directly, but that is not true for ruby-debug.)
4
+ class RuDebug::Page
5
+ def initialize_browser()
6
+ @browser_treeview = @glade["browser-treeview"]
7
+ @object_view = @glade["object-view"]
8
+ @object_buf = @object_view.buffer
9
+
10
+ @view_to_shell = @glade["view-to-shell-btn"]
11
+
12
+ @browser_store = Gtk::TreeStore.new(String, String, Integer, String, TrueClass)
13
+ @browser_treeview.model = @browser_store
14
+ @browser_treeview.rules_hint = true
15
+ renderer = Gtk::CellRendererText.new
16
+
17
+ create_default_tag(@object_buf, 10)
18
+
19
+ col = Gtk::TreeViewColumn.new("Attribute", renderer, :text => 0)
20
+ col.sizing = Gtk::TreeViewColumn::FIXED
21
+ col.reorderable = false
22
+ col.expand = true
23
+ col.resizable = true
24
+ @browser_treeview.append_column(col)
25
+ renderer = Gtk::CellRendererText.new()
26
+ renderer.size_points = 8
27
+ renderer.ellipsize = Pango::ELLIPSIZE_MIDDLE
28
+ col = Gtk::TreeViewColumn.new("Value", renderer, :text => 1)
29
+ col.sizing = Gtk::TreeViewColumn::FIXED
30
+ col.reorderable = false
31
+ col.expand = true
32
+ col.resizable = true
33
+ @browser_treeview.append_column(col)
34
+ renderer = Gtk::CellRendererText.new
35
+ col = Gtk::TreeViewColumn.new("OID", nil, :text => 2)
36
+ col.visible = false
37
+ @browser_treeview.append_column(col)
38
+ renderer = Gtk::CellRendererText.new
39
+ col = Gtk::TreeViewColumn.new("Class", renderer, :text => 3)
40
+ col.visible = false
41
+ @browser_treeview.append_column(col)
42
+ renderer = Gtk::CellRendererText.new
43
+ col = Gtk::TreeViewColumn.new("Virtual", renderer, :text => 4)
44
+ col.visible = false
45
+ @browser_treeview.append_column(col)
46
+
47
+ @browser_treeview.headers_visible = true
48
+ @browser_treeview.headers_clickable = false
49
+ @browser_treeview.enable_search = false
50
+ end
51
+
52
+ def load_browser()
53
+ load_data(@browser_treeview)
54
+ end
55
+
56
+ def attribute_for(iter) # Attribute description
57
+ iter[0]
58
+ end
59
+
60
+ def value_for(iter) # Value string
61
+ iter[1]
62
+ end
63
+
64
+ def oid_for(iter) # Object id
65
+ iter[2]
66
+ end
67
+
68
+ def class_for(iter) # Class string
69
+ iter[3]
70
+ end
71
+
72
+ # Virtual attributes are attributes that don't really exist that way.
73
+ # They are just information we poll for the user and should not get
74
+ # information like classes and so on.
75
+ def is_virtual?(iter) # Virtual attribute?
76
+ iter[4]
77
+ end
78
+
79
+ def on_browser_treeview_cursor_changed(treeview)
80
+ path, column = *treeview.cursor # We don't care about selected column
81
+ iter = treeview.model.get_iter(path)
82
+ text = [
83
+ value_for(iter),
84
+ #class_for(iter)
85
+ ].join("\n\n")
86
+
87
+ @object_buf.text = ""
88
+ highlight_inspect(text, @object_buf)
89
+
90
+ @session.eval("$rudebug.selected = %s" % pid_to_obj(oid_for(iter)))
91
+ @view_to_shell.sensitive = true
92
+ end
93
+
94
+ def on_view_to_shell_btn_clicked(button)
95
+ @shell_in.delete_selection
96
+ @shell_in.insert_at_cursor("$rudebug.selected")
97
+ @shell_in.grab_focus
98
+ sel_start, sel_end = *@shell_in.selection_bounds
99
+ @shell_in.select_region(sel_end, sel_end)
100
+ end
101
+
102
+ def root_code()
103
+ "[['context', false, #{eval_data("binding")}]]"
104
+ end
105
+
106
+ def dummy_attribute()
107
+ "DUMMY"
108
+ end
109
+
110
+ def on_browser_treeview_row_expanded(treeview, iter, path)
111
+ load_data(treeview, iter)
112
+ end
113
+
114
+ def pid_to_obj(pid)
115
+ "ObjectSpace._id2ref(%s)" % pid
116
+ end
117
+
118
+ # TODO: Refresh functionality?
119
+
120
+ def load_data(treeview, parent_iter = nil)
121
+ store = treeview.model
122
+ code = nil
123
+
124
+ if parent_iter.nil? then
125
+ treeview.model.clear
126
+ code = root_code() % []
127
+ else
128
+ dummy = parent_iter.first_child
129
+ if attribute_for(dummy) == dummy_attribute() then
130
+ # Remove dummy
131
+ store.remove(parent_iter.first_child)
132
+
133
+ parent_class = class_for(parent_iter)
134
+ pid = oid_for(parent_iter)
135
+
136
+ code_template = attributes_for(parent_class, is_virtual?(parent_iter))
137
+ code = code_template % pid_to_obj(pid)
138
+ end
139
+ end
140
+
141
+ if code then
142
+ attributes = eval(@session.eval(code))
143
+ attributes.each do |(attribute, virtual, value, klass, oid)|
144
+ iter = store.append(parent_iter)
145
+ iter[0], iter[1], iter[2], iter[3], iter[4] =
146
+ attribute, value, oid, klass, virtual
147
+
148
+ if can_expand?(iter, klass, oid) then
149
+ dummy_iter = store.append(iter)
150
+ dummy_iter[0] = dummy_attribute()
151
+ end
152
+ end
153
+
154
+ if parent_iter then
155
+ treeview.collapse_row(parent_iter.path)
156
+ treeview.expand_row(parent_iter.path, false)
157
+ end
158
+ end
159
+ end
160
+
161
+ def eval_data(code, needs_ref = false)
162
+ ref = needs_ref ? "$rudebug.add_ref(#{code})" : code
163
+ "$rudebug.inspect_obj((#{code})), (#{code}).class.to_s, (#{ref}).object_id"
164
+ end
165
+
166
+ def attributes_for(klass, virtual)
167
+ code_parts = []
168
+
169
+ hash_code =
170
+
171
+ code_parts << if !virtual then %<
172
+ (%1$s.methods(false).empty?() ? [] : [
173
+ [ "eigenclass", false,
174
+ #{eval_data("class << %1$s; self; end")} ]]
175
+ ) +
176
+
177
+ (%1$s.is_a?(Module) ? [] : [
178
+ [ "class", false,
179
+ #{eval_data("%1$s.class")} ]]
180
+ ) +
181
+
182
+ (%1$s.instance_variables.empty?() ? [] : [
183
+ [ "ivars", true,
184
+ #{eval_data(%<
185
+ $rudebug.to_pairs(%1$s.instance_variables.sort) do |rudebug_ivar|
186
+ %1$s.instance_variable_get(rudebug_ivar)
187
+ end>, true)
188
+ }]
189
+ ]) +
190
+
191
+ (!%1$s.respond_to?(:size) ? [] : [
192
+ [ "size", true,
193
+ #{eval_data("%1$s.size rescue 'error'")} ]]
194
+ )>
195
+ end
196
+
197
+ code_parts << if klass == "Binding" or klass == "Proc" then %<[
198
+ [ "location", true,
199
+ #{eval_data("eval('[__FILE__, __LINE__]', %1$s).join(':')", true)} ],
200
+ [ "self", false,
201
+ #{eval_data("eval('self', %1$s)")} ],
202
+ [ "lvars", true,
203
+ #{eval_data(%<
204
+ $rudebug.to_pairs(
205
+ (eval('local_variables', %1$s) +
206
+ (eval('$~.nil?', %1$s) ? [] : ['$~']) +
207
+ (eval('$_.nil?', %1$s) ? [] : ['$_']) -
208
+ ['_', 'bp', 'client']
209
+ ).sort
210
+ ) do |rudebug_lvar|
211
+ eval(rudebug_lvar, %1$s)
212
+ end>, true)
213
+ }],
214
+ [ "caller", true,
215
+ #{eval_data(%<$rudebug.to_list(eval('caller', %1$s))>, true)}
216
+ ]]>
217
+ elsif klass == "Array" then %<
218
+ (0 .. %1$s.size - 1).map do |rudebug_idx|
219
+ [ rudebug_idx.inspect, false,
220
+ #{eval_data("%1$s[rudebug_idx]")} ]
221
+ end>
222
+ elsif klass == "Hash" then %<
223
+ %1$s.map do |rudebug_key, rudebug_val|
224
+ [ rudebug_key.inspect, false,
225
+ #{eval_data("rudebug_val")} ]
226
+ end>
227
+ elsif klass == "$rudebug::Pairs" then %<
228
+ %1$s.map do |rudebug_key, rudebug_val|
229
+ [ rudebug_key.to_s, true,
230
+ #{eval_data("rudebug_val")} ]
231
+ end>
232
+ elsif klass == "Class" then %<
233
+ (%1$s == Object ? [] : [
234
+ [ "superclass", true,
235
+ #{eval_data("%1$s.superclass")} ]]
236
+ )>
237
+ elsif klass == "Object" then %<
238
+ (%1$s != ENV ? [] :
239
+ %1$s.map do |rudebug_key, rudebug_val|
240
+ [ rudebug_key.inspect, true,
241
+ #{eval_data("rudebug_val", true)} ]
242
+ end
243
+ )>
244
+ end
245
+
246
+ code_parts << if klass == "Class" or klass == "Module" then %<[
247
+ [ "modules", true,
248
+ #{eval_data("%1$s.included_modules", true)} ],
249
+ [ "constants", true,
250
+ #{eval_data(%<
251
+ $rudebug.to_pairs(%1$s.constants.sort) do |rudebug_const|
252
+ %1$s.const_get(rudebug_const)
253
+ end>, true)
254
+ }],
255
+ [ "imethods", true,
256
+ #{eval_data(%<
257
+ $rudebug.to_pairs(%1$s.instance_methods(false).sort) do |rudebug_imethod|
258
+ %1$s.instance_method(rudebug_imethod)
259
+ end>, true)
260
+ }]]>
261
+ end
262
+
263
+ code_parts.compact.map { |part| part.strip }.join(" + ")
264
+ end
265
+
266
+ def can_expand?(iter, klass, pid)
267
+ # No cyclic nodes
268
+ return false if is_cyclic?(iter)
269
+ code_template = attributes_for(klass, is_virtual?(iter))
270
+ # Short-circuit empty attributes (optimization)
271
+ return false if code_template == ""
272
+ #puts "template: %s" % code_template
273
+ code = code_template % pid_to_obj(pid)
274
+ #puts "code: %s" % code
275
+ result = @session.eval(code)
276
+ #p result
277
+ #p [code, result]
278
+ attributes = eval(result)
279
+ !attributes.empty?
280
+ end
281
+
282
+ def is_cyclic?(iter)
283
+ value = value_for(iter)
284
+
285
+ while iter = iter.parent
286
+ return true if value_for(iter) == value
287
+ end
288
+
289
+ return false
290
+ end
291
+ end