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.
@@ -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