blob: 74f8f8a4048005d2a99f006733342ac7e558e3ae [file] [log] [blame]
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +00001#! /usr/bin/env python
2
3import os
4import sys
5import string
Guido van Rossum5af7a721998-10-12 23:59:27 +00006import re
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +00007
8import linecache
9from code import InteractiveInterpreter
10
11from Tkinter import *
12import tkMessageBox
13
14from EditorWindow import fixwordbreaks
15from FileList import FileList, MultiEditorWindow, MultiIOBinding
16from ColorDelegator import ColorDelegator
17
18
Guido van Rossum5af7a721998-10-12 23:59:27 +000019class PyShellEditorWindow(MultiEditorWindow):
20
21 def fixedwindowsmenu(self, wmenu):
22 wmenu.add_command(label="Python Shell", command=self.flist.open_shell)
23
24
25class PyShellFileList(FileList):
26
27 EditorWindow = PyShellEditorWindow
28
29 pyshell = None
30
31 def open_shell(self):
32 if self.pyshell:
33 self.pyshell.wakeup()
34 else:
35 self.pyshell = PyShell(self)
36 self.pyshell.begin()
37 return self.pyshell
38
39
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000040class ModifiedIOBinding(MultiIOBinding):
41
42 def defaultfilename(self, mode="open"):
43 if self.filename:
44 return MultiIOBinding.defaultfilename(self, mode)
45 else:
46 try:
47 pwd = os.getcwd()
48 except os.error:
49 pwd = ""
50 return pwd, ""
51
52 def open(self, event):
53 # Override base class method -- don't allow reusing this window
54 filename = self.askopenfile()
55 if filename:
56 self.flist.open(filename)
57 return "break"
58
59 def maybesave(self):
60 # Override base class method -- don't ask any questions
61 if self.text.get_saved():
62 return "yes"
63 else:
64 return "no"
65
66
67class ModifiedColorDelegator(ColorDelegator):
68
69 def recolorize_main(self):
70 self.tag_remove("TODO", "1.0", "iomark")
71 self.tag_add("SYNC", "1.0", "iomark")
72 ColorDelegator.recolorize_main(self)
73
74
75class ModifiedInterpreter(InteractiveInterpreter):
76
77 def __init__(self, tkconsole):
78 self.tkconsole = tkconsole
79 InteractiveInterpreter.__init__(self)
80
81 gid = 0
82
83 def runsource(self, source):
84 # Extend base class to stuff the source in the line cache
85 filename = "<console#%d>" % self.gid
86 self.gid = self.gid + 1
87 lines = string.split(source, "\n")
88 linecache.cache[filename] = len(source)+1, 0, lines, filename
89 self.more = 0
90 return InteractiveInterpreter.runsource(self, source, filename)
91
92 def showsyntaxerror(self, filename=None):
93 # Extend base class to color the offending position
94 # (instead of printing it and pointing at it with a caret)
95 text = self.tkconsole.text
96 stuff = self.unpackerror()
97 if not stuff:
98 self.tkconsole.resetoutput()
99 InteractiveInterpreter.showsyntaxerror(self, filename)
100 return
101 msg, lineno, offset, line = stuff
102 if lineno == 1:
103 pos = "iomark + %d chars" % (offset-1)
104 else:
105 pos = "iomark linestart + %d lines + %d chars" % (lineno-1,
106 offset-1)
107 text.tag_add("ERROR", pos)
108 text.see(pos)
109 char = text.get(pos)
110 if char in string.letters + string.digits + "_":
111 text.tag_add("ERROR", pos + " wordstart", pos)
112 self.tkconsole.resetoutput()
113 self.write("SyntaxError: %s\n" % str(msg))
114
115 def unpackerror(self):
116 type, value, tb = sys.exc_info()
117 ok = type == SyntaxError
118 if ok:
119 try:
120 msg, (dummy_filename, lineno, offset, line) = value
121 except:
122 ok = 0
123 if ok:
124 return msg, lineno, offset, line
125 else:
126 return None
127
128 def showtraceback(self):
129 # Extend base class method to reset output properly
130 text = self.tkconsole.text
131 self.tkconsole.resetoutput()
Guido van Rossum19563521998-10-13 16:32:05 +0000132 self.checklinecache()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000133 InteractiveInterpreter.showtraceback(self)
Guido van Rossum19563521998-10-13 16:32:05 +0000134
135 def checklinecache(self):
136 c = linecache.cache
137 for key in c.keys():
138 if key[:1] + key[-1:] != "<>":
139 del c[key]
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000140
141 def runcode(self, code):
142 # Override base class method
143 try:
144 self.tkconsole.beginexecuting()
145 try:
146 exec code in self.locals
147 except SystemExit:
148 if tkMessageBox.askyesno(
149 "Exit?",
150 "Do you want to exit altogether?",
151 default="yes",
152 master=self.tkconsole.text):
153 raise
154 else:
155 self.showtraceback()
156 except:
157 self.showtraceback()
158 finally:
159 self.tkconsole.endexecuting()
160
161 def write(self, s):
162 # Override base class write
163 self.tkconsole.console.write(s)
164
165
Guido van Rossum5af7a721998-10-12 23:59:27 +0000166class PyShell(PyShellEditorWindow):
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000167
168 # Override classes
169 ColorDelegator = ModifiedColorDelegator
170 IOBinding = ModifiedIOBinding
171
Guido van Rossum5af7a721998-10-12 23:59:27 +0000172 # Override menu bar specs
173 menu_specs = PyShellEditorWindow.menu_specs[:]
174 menu_specs.insert(len(menu_specs)-1, ("debug", "Debug"))
175
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000176 # New class
177 from History import History
178
179 def __init__(self, flist=None):
180 self.interp = ModifiedInterpreter(self)
181 if flist is None:
182 root = Tk()
183 fixwordbreaks(root)
184 root.withdraw()
Guido van Rossum5af7a721998-10-12 23:59:27 +0000185 flist = PyShellFileList(root)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000186
Guido van Rossum5af7a721998-10-12 23:59:27 +0000187 PyShellEditorWindow.__init__(self, flist, None, None)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000188 self.config_colors()
189
190 import __builtin__
191 __builtin__.quit = __builtin__.exit = "To exit, type Ctrl-D."
192
193 self.auto.config(prefertabs=1)
194
195 text = self.text
196 text.bind("<<newline-and-indent>>", self.enter_callback)
197 text.bind("<<plain-newline-and-indent>>", self.linefeed_callback)
198 text.bind("<<interrupt-execution>>", self.cancel_callback)
199 text.bind("<<beginning-of-line>>", self.home_callback)
200 text.bind("<<end-of-file>>", self.eof_callback)
Guido van Rossum5af7a721998-10-12 23:59:27 +0000201 text.bind("<<goto-traceback-line>>", self.goto_traceback_line)
202 text.bind("<<open-stack-viewer>>", self.open_stack_viewer)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000203
204 sys.stdout = PseudoFile(self, "stdout")
Guido van Rossum3f08d401998-10-13 15:21:41 +0000205 sys.stderr = PseudoFile(self, "stderr")
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000206 sys.stdin = self
207 self.console = PseudoFile(self, "console")
208
209 self.history = self.History(self.text)
210
211 tagdefs = {
212 ##"stdin": {"background": "yellow"},
213 "stdout": {"foreground": "blue"},
214 "stderr": {"foreground": "#007700"},
215 "console": {"foreground": "red"},
216 "ERROR": {"background": "#FF7777"},
217 None: {"foreground": "purple"}, # default
218 }
219
220 def config_colors(self):
221 for tag, cnf in self.tagdefs.items():
222 if cnf:
223 if not tag:
224 apply(self.text.configure, (), cnf)
225 else:
226 apply(self.text.tag_configure, (tag,), cnf)
227
228 reading = 0
229 executing = 0
230 canceled = 0
231 endoffile = 0
232
233 def beginexecuting(self):
234 # Helper for ModifiedInterpreter
235 self.resetoutput()
236 self.executing = 1
237 self._cancel_check = self.cancel_check
238 ##sys.settrace(self._cancel_check)
239
240 def endexecuting(self):
241 # Helper for ModifiedInterpreter
242 sys.settrace(None)
243 self.executing = 0
244 self.canceled = 0
245
246 def close(self):
247 # Extend base class method
248 if self.executing:
249 # XXX Need to ask a question here
250 if not tkMessageBox.askokcancel(
251 "Cancel?",
252 "The program is still running; do you want to cancel it?",
253 default="ok",
254 master=self.text):
255 return "cancel"
256 self.canceled = 1
257 if self.reading:
258 self.top.quit()
259 return "cancel"
Guido van Rossum5af7a721998-10-12 23:59:27 +0000260 reply = PyShellEditorWindow.close(self)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000261 if reply != "cancel":
Guido van Rossum5af7a721998-10-12 23:59:27 +0000262 self.flist.pyshell = None
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000263 # Restore std streams
264 sys.stdout = sys.__stdout__
265 sys.stderr = sys.__stderr__
266 sys.stdin = sys.__stdin__
267 # Break cycles
268 self.interp = None
269 self.console = None
270 return reply
271
272 def ispythonsource(self, filename):
273 # Override this so EditorWindow never removes the colorizer
274 return 1
275
276 def saved_change_hook(self):
277 # Override this to get the title right
278 title = "Python Shell"
279 if self.io.filename:
280 title = title + ": " + self.io.filename
281 if not self.undo.get_saved():
282 title = title + " *"
283 self.top.wm_title(title)
284
Guido van Rossum5af7a721998-10-12 23:59:27 +0000285 def begin(self):
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000286 self.resetoutput()
287 self.write("Python %s on %s\n%s\n" %
288 (sys.version, sys.platform, sys.copyright))
289 try:
290 sys.ps1
291 except AttributeError:
292 sys.ps1 = ">>> "
293 self.showprompt()
294 import Tkinter
295 Tkinter._default_root = None
Guido van Rossum5af7a721998-10-12 23:59:27 +0000296
297 def interact(self):
298 self.begin()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000299 self.top.mainloop()
300
301 def readline(self):
302 save = self.reading
303 try:
304 self.reading = 1
305 self.top.mainloop()
306 finally:
307 self.reading = save
308 line = self.text.get("iomark", "end-1c")
309 self.resetoutput()
310 if self.canceled:
311 self.canceled = 0
312 raise KeyboardInterrupt
313 if self.endoffile:
314 self.endoffile = 0
315 return ""
316 return line
317
318 def cancel_callback(self, event):
319 try:
320 if self.text.compare("sel.first", "!=", "sel.last"):
321 return # Active selection -- always use default binding
322 except:
323 pass
324 if not (self.executing or self.reading):
325 self.resetoutput()
326 self.write("KeyboardInterrupt\n")
327 self.showprompt()
328 return "break"
329 self.endoffile = 0
330 self.canceled = 1
331 if self.reading:
332 self.top.quit()
333 return "break"
334
335 def eof_callback(self, event):
336 if self.executing and not self.reading:
337 return # Let the default binding (delete next char) take over
338 if not (self.text.compare("iomark", "==", "insert") and
339 self.text.compare("insert", "==", "end-1c")):
340 return # Let the default binding (delete next char) take over
341 if not self.executing:
342## if not tkMessageBox.askokcancel(
343## "Exit?",
344## "Are you sure you want to exit?",
345## default="ok", master=self.text):
346## return "break"
347 self.resetoutput()
348 self.close()
349 else:
350 self.canceled = 0
351 self.endoffile = 1
352 self.top.quit()
353 return "break"
354
355 def home_callback(self, event):
356 if event.state != 0 and event.keysym == "Home":
357 return # <Modifier-Home>; fall back to class binding
358 if self.text.compare("iomark", "<=", "insert") and \
359 self.text.compare("insert linestart", "<=", "iomark"):
360 self.text.mark_set("insert", "iomark")
361 self.text.tag_remove("sel", "1.0", "end")
362 self.text.see("insert")
363 return "break"
364
365 def linefeed_callback(self, event):
366 # Insert a linefeed without entering anything (still autoindented)
367 if self.reading:
368 self.text.insert("insert", "\n")
369 self.text.see("insert")
370 else:
371 self.auto.autoindent(event)
372 return "break"
373
374 def enter_callback(self, event):
375 if self.executing and not self.reading:
376 return # Let the default binding (insert '\n') take over
377 # If some text is selected, recall the selection
Guido van Rossum4650df91998-10-13 14:41:27 +0000378 # (but only if this before the I/O mark)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000379 try:
380 sel = self.text.get("sel.first", "sel.last")
381 if sel:
Guido van Rossum4650df91998-10-13 14:41:27 +0000382 if self.text.compare("self.last", "<=", "iomark"):
383 self.recall(sel)
384 return "break"
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000385 except:
386 pass
387 # If we're strictly before the line containing iomark, recall
388 # the current line, less a leading prompt, less leading or
389 # trailing whitespace
390 if self.text.compare("insert", "<", "iomark linestart"):
Guido van Rossum4650df91998-10-13 14:41:27 +0000391 # Check if there's a relevant stdin range -- if so, use it
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000392 prev = self.text.tag_prevrange("stdin", "insert")
393 if prev and self.text.compare("insert", "<", prev[1]):
394 self.recall(self.text.get(prev[0], prev[1]))
395 return "break"
396 next = self.text.tag_nextrange("stdin", "insert")
397 if next and self.text.compare("insert lineend", ">=", next[0]):
398 self.recall(self.text.get(next[0], next[1]))
399 return "break"
400 # No stdin mark -- just get the current line
401 self.recall(self.text.get("insert linestart", "insert lineend"))
402 return "break"
Guido van Rossum4650df91998-10-13 14:41:27 +0000403 # If we're in the current input before its last line,
404 # insert a newline right at the insert point
405 if self.text.compare("insert", "<", "end-1c linestart"):
406 self.auto.autoindent(event)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000407 return "break"
Guido van Rossum4650df91998-10-13 14:41:27 +0000408 # We're in the last line; append a newline and submit it
409 self.text.mark_set("insert", "end-1c")
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000410 if self.reading:
411 self.text.insert("insert", "\n")
412 self.text.see("insert")
413 else:
414 self.auto.autoindent(event)
415 self.text.tag_add("stdin", "iomark", "end-1c")
416 self.text.update_idletasks()
417 if self.reading:
418 self.top.quit() # Break out of recursive mainloop() in raw_input()
419 else:
420 self.runit()
421 return "break"
422
423 def recall(self, s):
424 if self.history:
425 self.history.recall(s)
426
427 def runit(self):
428 line = self.text.get("iomark", "end-1c")
429 # Strip off last newline and surrounding whitespace.
430 # (To allow you to hit return twice to end a statement.)
431 i = len(line)
432 while i > 0 and line[i-1] in " \t":
433 i = i-1
434 if i > 0 and line[i-1] == "\n":
435 i = i-1
436 while i > 0 and line[i-1] in " \t":
437 i = i-1
438 line = line[:i]
439 more = self.interp.runsource(line)
440 if not more:
441 self.showprompt()
442
443 def cancel_check(self, frame, what, args,
444 dooneevent=tkinter.dooneevent,
445 dontwait=tkinter.DONT_WAIT):
446 # Hack -- use the debugger hooks to be able to handle events
447 # and interrupt execution at any time.
448 # This slows execution down quite a bit, so you may want to
449 # disable this (by not calling settrace() in runcode() above)
450 # for full-bore (uninterruptable) speed.
451 # XXX This should become a user option.
452 if self.canceled:
453 return
454 dooneevent(dontwait)
455 if self.canceled:
456 self.canceled = 0
457 raise KeyboardInterrupt
458 return self._cancel_check
Guido van Rossum5af7a721998-10-12 23:59:27 +0000459
460 file_line_pats = [
461 r'File "([^"]*)", line (\d+)',
462 r'([^\s]+)\((\d+)\)',
463 r'([^\s]+):\s*(\d+):',
464 ]
465
466 file_line_progs = None
467
468 def goto_traceback_line(self, event=None):
469 if self.file_line_progs is None:
470 l = []
471 for pat in self.file_line_pats:
472 l.append(re.compile(pat))
473 self.file_line_progs = l
474 # x, y = self.event.x, self.event.y
475 # self.text.mark_set("insert", "@%d,%d" % (x, y))
476 line = self.text.get("insert linestart", "insert lineend")
477 for prog in self.file_line_progs:
478 m = prog.search(line)
479 if m:
480 break
481 else:
482 tkMessageBox.showerror("No traceback line",
483 "The line you point at doesn't look "
484 "like an error message.",
485 master=self.text)
486 return
487 filename, lineno = m.group(1, 2)
488 try:
489 f = open(filename, "r")
490 f.close()
491 except IOError, msg:
492 self.text.bell()
493 return
494 edit = self.flist.open(filename)
495 try:
496 lineno = int(lineno)
497 except ValueError, msg:
498 self.text.bell()
499 return
500 edit.gotoline(lineno)
501
502 def open_stack_viewer(self, event=None):
503 try:
504 sys.last_traceback
505 except:
506 tkMessageBox.showerror("No stack trace",
507 "There is no stack trace yet.\n"
508 "(sys.last_traceback is not defined)",
509 master=self.text)
510 return
511 from StackViewer import StackViewer
512 sv = StackViewer(self.root, self.flist)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000513
514 def showprompt(self):
515 self.resetoutput()
516 try:
517 s = str(sys.ps1)
518 except:
519 s = ""
520 self.console.write(s)
521 self.text.mark_set("insert", "end-1c")
522
523 def resetoutput(self):
524 source = self.text.get("iomark", "end-1c")
525 if self.history:
526 self.history.history_store(source)
527 if self.text.get("end-2c") != "\n":
528 self.text.insert("end-1c", "\n")
529 self.text.mark_set("iomark", "end-1c")
530 sys.stdout.softspace = 0
531
532 def write(self, s):
533 # Overrides base class write
534 self.console.write(s)
535
536class PseudoFile:
537
538 def __init__(self, interp, tags):
539 self.interp = interp
540 self.text = interp.text
541 self.tags = tags
542
543 def write(self, s):
544 self.text.mark_gravity("iomark", "right")
545 self.text.insert("iomark", str(s), self.tags)
546 self.text.mark_gravity("iomark", "left")
547 self.text.see("iomark")
548 self.text.update()
549 if self.interp.canceled:
550 self.interp.canceled = 0
551 raise KeyboardInterrupt
552
553 def writelines(self, l):
554 map(self.write, l)
555
556
557def main():
558 global flist, root
559 root = Tk()
560 fixwordbreaks(root)
561 root.withdraw()
Guido van Rossum5af7a721998-10-12 23:59:27 +0000562 flist = PyShellFileList(root)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000563 if sys.argv[1:]:
564 for filename in sys.argv[1:]:
565 flist.open(filename)
566 t = PyShell(flist)
Guido van Rossum5af7a721998-10-12 23:59:27 +0000567 flist.pyshell = t
568 t.begin()
569 root.mainloop()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000570
571if __name__ == "__main__":
572 main()