Class: Teek::Debugger

Inherits:
Object
  • Object
show all
Defined in:
lib/teek/debugger.rb

Overview

Live inspector for Teek applications. Opens a Toplevel window with three tabs: Widgets (tree + config), Variables (searchable list), and Watches (tracked variable history).

Can also be enabled via the TEEK_DEBUG environment variable.

Examples:

app = Teek::App.new(debug: true)

Constant Summary collapse

TOP =

Prefix for all debugger widget paths

".teek_debug"
NB =
"#{TOP}.nb"
WATCH_HISTORY_SIZE =
50

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app) ⇒ Debugger

Returns a new instance of Debugger.



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/teek/debugger.rb', line 20

def initialize(app)
  @app = app
  @interp = app.interp
  @watches = {}

  app.command(:toplevel, TOP)
  app.command(:wm, 'title', TOP, 'Teek Debugger')
  app.command(:wm, 'geometry', TOP, '400x500')

  # Don't let closing the debugger kill the app
  close_proc = proc { |*| app.command(:wm, 'withdraw', TOP) }
  app.command(:wm, 'protocol', TOP, 'WM_DELETE_WINDOW', close_proc)

  setup_ui
  sync_widget_tree
  start_auto_refresh

  # Start behind the main app window
  app.command(:lower, TOP, '.')
end

Instance Attribute Details

#interpObject (readonly)

Returns the value of attribute interp.



18
19
20
# File 'lib/teek/debugger.rb', line 18

def interp
  @interp
end

Instance Method Details

#add_watch(name) ⇒ Object

Add a variable watch by name. Registers a Tcl trace so changes are recorded.



79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/teek/debugger.rb', line 79

def add_watch(name)
  return if @watches.key?(name)

  cb_id = @app.register_callback(proc { |var_name, index, *|
    record_watch(var_name, index)
  })

  @app.tcl_eval("trace add variable #{Teek.make_list(name)} write {ruby_callback #{cb_id}}")

  @watches[name] = { cb_id: cb_id, values: [] }
  record_watch(name, nil)
  update_watches_ui
end

#hideObject



46
47
48
# File 'lib/teek/debugger.rb', line 46

def hide
  @app.hide(TOP)
end

#on_widget_created(path, cls) ⇒ Object

Called by App when a widget is created



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/teek/debugger.rb', line 51

def on_widget_created(path, cls)
  tree = "#{NB}.widgets.tree"
  return unless @app.command(:winfo, 'exists', tree) == "1"
  return if @app.command(tree, 'exists', path) == "1"

  ensure_parent_exists(path)
  parent_id = parent_tree_id(path)
  name = tk_basename(path)

  @app.command(tree, 'insert', parent_id, 'end',
    id: path, text: name, values: Teek.make_list(path, cls))
  @app.command(tree, 'item', parent_id, open: 1)
rescue Teek::TclError => e
  $stderr.puts "teek debugger: on_widget_created(#{path}): #{e.message}"
end

#on_widget_destroyed(path) ⇒ Object

Called by App when a widget is destroyed



68
69
70
71
72
73
74
75
76
# File 'lib/teek/debugger.rb', line 68

def on_widget_destroyed(path)
  tree = "#{NB}.widgets.tree"
  return unless @app.command(:winfo, 'exists', tree) == "1"
  return unless @app.command(tree, 'exists', path) == "1"

  @app.command(tree, 'delete', path)
rescue Teek::TclError => e
  $stderr.puts "teek debugger: on_widget_destroyed(#{path}): #{e.message}"
end

#remove_watch(name) ⇒ Object

Remove a variable watch by name.



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/teek/debugger.rb', line 94

def remove_watch(name)
  info = @watches.delete(name)
  return unless info

  @app.tcl_eval(
    "trace remove variable #{Teek.make_list(name)} write {ruby_callback #{info[:cb_id]}}"
  )
  @app.unregister_callback(info[:cb_id])

  # Remove the tree item
  watch_tree = "#{NB}.watches.tree"
  item_id = "watch_#{name}"
  if @app.command(watch_tree, 'exists', item_id) == "1"
    @app.command(watch_tree, 'delete', item_id)
  end

  update_watches_ui
rescue Teek::TclError => e
  $stderr.puts "teek debugger: remove_watch(#{name}): #{e.message}"
end

#showObject



41
42
43
44
# File 'lib/teek/debugger.rb', line 41

def show
  @app.show(TOP)
  @app.command(:raise, TOP)
end