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