blob: dbfc4b012717d552986d4b27132b3b6d22de3fc6 [file] [log] [blame]
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +00001import string
2import sys
3import os
4from Tkinter import *
5import linecache
6from repr import Repr
7
8
9class StackViewer:
10
11 def __init__(self, root=None, flist=None):
12 self.flist = flist
13 # Create root and/or toplevel window
14 if not root:
15 import Tkinter
16 root = Tkinter._default_root
17 if not root:
18 root = top = Tk()
19 else:
20 top = Toplevel(root)
21 self.root = root
22 self.top = top
23 top.wm_title("Stack viewer")
Guido van Rossum8571ed81998-10-10 19:15:32 +000024 # Create help label
25 self.helplabel = Label(top,
26 text="Click once to view variables; twice for source",
27 borderwidth=2, relief="groove")
28 self.helplabel.pack(fill="x")
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000029 # Create top frame, with scrollbar and listbox
30 self.topframe = Frame(top)
31 self.topframe.pack(fill="both", expand=1)
32 self.vbar = Scrollbar(self.topframe, name="vbar")
33 self.vbar.pack(side="right", fill="y")
34 self.listbox = Listbox(self.topframe, exportselection=0,
35 takefocus=1, width=60)
36 self.listbox.pack(expand=1, fill="both")
37 # Tie listbox and scrollbar together
38 self.vbar["command"] = self.listbox.yview
39 self.listbox["yscrollcommand"] = self.vbar.set
40 # Bind events to the list box
41 self.listbox.bind("<ButtonRelease-1>", self.click_event)
42 self.listbox.bind("<Double-ButtonRelease-1>", self.double_click_event)
43 self.listbox.bind("<ButtonPress-3>", self.popup_event)
44 self.listbox.bind("<Key-Up>", self.up_event)
45 self.listbox.bind("<Key-Down>", self.down_event)
Guido van Rossum8571ed81998-10-10 19:15:32 +000046 # Create status label
47 self.statuslabel = Label(top, text="status")
48 self.statuslabel.pack(fill="x")
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000049 # Load the stack
50 linecache.checkcache()
51 stack = getstack()
52 self.load_stack(stack)
Guido van Rossum8571ed81998-10-10 19:15:32 +000053 self.statuslabel.config(text=getexception())
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000054
55 def load_stack(self, stack):
56 self.stack = stack
57 l = self.listbox
58 l.delete(0, END)
59 if len(stack) > 10:
60 l["height"] = 10
61 self.topframe.pack(expand=1)
62 else:
63 l["height"] = len(stack)
64 self.topframe.pack(expand=0)
65 for frame, lineno in stack:
66 try:
67 modname = frame.f_globals["__name__"]
68 except:
69 modname = "?"
70 code = frame.f_code
71 filename = code.co_filename
72 funcname = code.co_name
73 sourceline = linecache.getline(filename, lineno)
74 sourceline = string.strip(sourceline)
75 if funcname in ("?", "", None):
76 item = "%s, line %d: %s" % (modname, lineno, sourceline)
77 else:
78 item = "%s.%s(), line %d: %s" % (modname, funcname,
79 lineno, sourceline)
80 l.insert(END, item)
81 l.focus_set()
82 l.selection_clear(0, "end")
83 l.activate("end")
84 l.see("end")
85
86 rmenu = None
87
88 def click_event(self, event):
89 self.listbox.activate("@%d,%d" % (event.x, event.y))
90 self.show_stack_frame()
91 return "break"
92
93 def popup_event(self, event):
94 if not self.rmenu:
95 self.make_menu()
96 rmenu = self.rmenu
97 self.event = event
98 self.listbox.activate("@%d,%d" % (event.x, event.y))
99 rmenu.tk_popup(event.x_root, event.y_root)
100
101 def make_menu(self):
102 rmenu = Menu(self.top, tearoff=0)
103 rmenu.add_command(label="Go to source line",
104 command=self.goto_source_line)
105 rmenu.add_command(label="Show stack frame",
106 command=self.show_stack_frame)
107 self.rmenu = rmenu
108
109 def goto_source_line(self):
110 index = self.listbox.index("active")
111 self.show_source(index)
112
113 def show_stack_frame(self):
114 index = self.listbox.index("active")
115 self.show_frame(index)
116
117 def double_click_event(self, event):
118 index = self.listbox.index("active")
119 self.show_source(index)
120 return "break"
121
122 def up_event(self, event):
123 index = self.listbox.index("active") - 1
124 if index < 0:
125 self.top.bell()
126 return "break"
127 self.show_frame(index)
128 return "break"
129
130 def down_event(self, event):
131 index = self.listbox.index("active") + 1
132 if index >= len(self.stack):
133 self.top.bell()
134 return "break"
135 self.show_frame(index)
136 return "break"
137
138 def show_source(self, index):
139 if not 0 <= index < len(self.stack):
140 self.top.bell()
141 return
142 frame, lineno = self.stack[index]
143 code = frame.f_code
144 filename = code.co_filename
145 if not self.flist:
146 self.top.bell()
147 return
148 if not os.path.exists(filename):
149 self.top.bell()
150 return
151 edit = self.flist.open(filename)
152 edit.gotoline(lineno)
153
154 localsframe = None
155 localsviewer = None
156 localsdict = None
157 globalsframe = None
158 globalsviewer = None
159 globalsdict = None
160 curframe = None
161
162 def show_frame(self, index):
163 if not 0 <= index < len(self.stack):
164 self.top.bell()
165 return
166 self.listbox.selection_clear(0, "end")
167 self.listbox.selection_set(index)
168 self.listbox.activate(index)
169 self.listbox.see(index)
170 self.listbox.focus_set()
171 frame, lineno = self.stack[index]
172 if frame is self.curframe:
173 return
174 self.curframe = None
175 if frame.f_globals is not self.globalsdict:
176 self.show_globals(frame)
177 self.show_locals(frame)
178 self.curframe = frame
179
180 def show_globals(self, frame):
181 title = "Global Variables"
182 if frame.f_globals.has_key("__name__"):
183 try:
184 name = str(frame.f_globals["__name__"]) + ""
185 except:
186 name = ""
187 if name:
188 title = title + " in module " + name
189 self.globalsdict = None
190 if self.globalsviewer:
191 self.globalsviewer.close()
192 self.globalsviewer = None
193 if not self.globalsframe:
194 self.globalsframe = Frame(self.top)
195 self.globalsdict = frame.f_globals
196 self.globalsviewer = NamespaceViewer(
197 self.globalsframe,
198 title,
199 self.globalsdict)
200 self.globalsframe.pack(fill="both", side="bottom")
201
202 def show_locals(self, frame):
203 self.localsdict = None
204 if self.localsviewer:
205 self.localsviewer.close()
206 self.localsviewer = None
207 if frame.f_locals is not frame.f_globals:
208 title = "Local Variables"
209 code = frame.f_code
210 funcname = code.co_name
211 if funcname not in ("?", "", None):
212 title = title + " in " + funcname
213 if not self.localsframe:
214 self.localsframe = Frame(self.top)
215 self.localsdict = frame.f_locals
216 self.localsviewer = NamespaceViewer(
217 self.localsframe,
218 title,
219 self.localsdict)
220 self.localsframe.pack(fill="both", side="top")
221 else:
222 if self.localsframe:
223 self.localsframe.forget()
224
225
226def getstack(t=None, f=None):
227 if t is None:
228 t = sys.last_traceback
229 stack = []
230 if t and t.tb_frame is f:
231 t = t.tb_next
232 while f is not None:
233 stack.append((f, f.f_lineno))
234 if f is self.botframe:
235 break
236 f = f.f_back
237 stack.reverse()
238 while t is not None:
239 stack.append((t.tb_frame, t.tb_lineno))
240 t = t.tb_next
241 return stack
242
243
Guido van Rossum8571ed81998-10-10 19:15:32 +0000244def getexception(type=None, value=None):
245 if type is None:
246 type = sys.last_type
247 value = sys.last_value
248 if hasattr(type, "__name__"):
249 type = type.__name__
250 s = str(type)
251 if value is not None:
252 s = s + ": " + str(value)
253 return s
254
255
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000256class NamespaceViewer:
257
258 def __init__(self, frame, title, dict):
259 width = 0
260 height = 20*len(dict) # XXX 20 == observed height of Entry widget
261 self.frame = frame
262 self.title = title
263 self.dict = dict
264 self.repr = Repr()
265 self.repr.maxstring = 60
266 self.repr.maxother = 60
267 self.label = Label(frame, text=title, borderwidth=2, relief="groove")
268 self.label.pack(fill="x")
269 self.vbar = vbar = Scrollbar(frame, name="vbar")
270 vbar.pack(side="right", fill="y")
271 self.canvas = canvas = Canvas(frame,
272 height=min(300, max(40, height)),
273 scrollregion=(0, 0, width, height))
274 canvas.pack(side="left", fill="both", expand=1)
275 vbar["command"] = canvas.yview
276 canvas["yscrollcommand"] = vbar.set
277 self.subframe = subframe = Frame(canvas)
278 self.sfid = canvas.create_window(0, 0, window=subframe, anchor="nw")
279 names = dict.keys()
280 names.sort()
281 row = 0
282 for name in names:
283 value = dict[name]
284 svalue = self.repr.repr(value) # repr(value)
285 l = Label(subframe, text=name)
286 l.grid(row=row, column=0, sticky="nw")
287## l = Label(subframe, text=svalue, justify="l", wraplength=300)
288 l = Entry(subframe, width=0, borderwidth=0)
289 l.insert(0, svalue)
290## l["state"] = "disabled"
291 l.grid(row=row, column=1, sticky="nw")
292 row = row+1
293 frame.update_idletasks() # Alas!
294 width = subframe.winfo_reqwidth()
295 height = subframe.winfo_reqheight()
296 canvas["scrollregion"] = (0, 0, width, height)
297 if height > 300:
298 canvas["height"] = 300
299 frame.pack(expand=1)
300 else:
301 canvas["height"] = height
302 frame.pack(expand=0)
303
304 def close(self):
305 for c in self.subframe, self.label, self.vbar, self.canvas:
306 try:
307 c.destroy()
308 except:
309 pass