blob: a794defbfb9464c390b165216f776f0b5c4986a7 [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)
Guido van Rossum35f75421998-10-13 23:51:13 +000023 wmenu.add_separator()
Guido van Rossum5af7a721998-10-12 23:59:27 +000024
25
26class PyShellFileList(FileList):
27
28 EditorWindow = PyShellEditorWindow
29
30 pyshell = None
31
32 def open_shell(self):
33 if self.pyshell:
34 self.pyshell.wakeup()
35 else:
36 self.pyshell = PyShell(self)
37 self.pyshell.begin()
38 return self.pyshell
39
40
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000041class ModifiedIOBinding(MultiIOBinding):
42
43 def defaultfilename(self, mode="open"):
44 if self.filename:
45 return MultiIOBinding.defaultfilename(self, mode)
46 else:
47 try:
48 pwd = os.getcwd()
49 except os.error:
50 pwd = ""
51 return pwd, ""
52
53 def open(self, event):
54 # Override base class method -- don't allow reusing this window
55 filename = self.askopenfile()
56 if filename:
57 self.flist.open(filename)
58 return "break"
59
60 def maybesave(self):
61 # Override base class method -- don't ask any questions
62 if self.text.get_saved():
63 return "yes"
64 else:
65 return "no"
66
67
68class ModifiedColorDelegator(ColorDelegator):
69
70 def recolorize_main(self):
71 self.tag_remove("TODO", "1.0", "iomark")
72 self.tag_add("SYNC", "1.0", "iomark")
73 ColorDelegator.recolorize_main(self)
74
75
76class ModifiedInterpreter(InteractiveInterpreter):
77
78 def __init__(self, tkconsole):
79 self.tkconsole = tkconsole
80 InteractiveInterpreter.__init__(self)
81
82 gid = 0
83
84 def runsource(self, source):
85 # Extend base class to stuff the source in the line cache
86 filename = "<console#%d>" % self.gid
87 self.gid = self.gid + 1
88 lines = string.split(source, "\n")
89 linecache.cache[filename] = len(source)+1, 0, lines, filename
90 self.more = 0
91 return InteractiveInterpreter.runsource(self, source, filename)
92
93 def showsyntaxerror(self, filename=None):
94 # Extend base class to color the offending position
95 # (instead of printing it and pointing at it with a caret)
96 text = self.tkconsole.text
97 stuff = self.unpackerror()
98 if not stuff:
99 self.tkconsole.resetoutput()
100 InteractiveInterpreter.showsyntaxerror(self, filename)
101 return
102 msg, lineno, offset, line = stuff
103 if lineno == 1:
104 pos = "iomark + %d chars" % (offset-1)
105 else:
106 pos = "iomark linestart + %d lines + %d chars" % (lineno-1,
107 offset-1)
108 text.tag_add("ERROR", pos)
109 text.see(pos)
110 char = text.get(pos)
111 if char in string.letters + string.digits + "_":
112 text.tag_add("ERROR", pos + " wordstart", pos)
113 self.tkconsole.resetoutput()
114 self.write("SyntaxError: %s\n" % str(msg))
115
116 def unpackerror(self):
117 type, value, tb = sys.exc_info()
118 ok = type == SyntaxError
119 if ok:
120 try:
121 msg, (dummy_filename, lineno, offset, line) = value
122 except:
123 ok = 0
124 if ok:
125 return msg, lineno, offset, line
126 else:
127 return None
128
129 def showtraceback(self):
130 # Extend base class method to reset output properly
131 text = self.tkconsole.text
132 self.tkconsole.resetoutput()
Guido van Rossum19563521998-10-13 16:32:05 +0000133 self.checklinecache()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000134 InteractiveInterpreter.showtraceback(self)
Guido van Rossum19563521998-10-13 16:32:05 +0000135
136 def checklinecache(self):
137 c = linecache.cache
138 for key in c.keys():
139 if key[:1] + key[-1:] != "<>":
140 del c[key]
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000141
Guido van Rossum35f75421998-10-13 23:51:13 +0000142 debugger = None
143
144 def setdebugger(self, debugger):
145 self.debugger = debugger
146
147 def getdebugger(self):
148 return self.debugger
149
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000150 def runcode(self, code):
151 # Override base class method
Guido van Rossum35f75421998-10-13 23:51:13 +0000152 debugger = self.debugger
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000153 try:
154 self.tkconsole.beginexecuting()
155 try:
Guido van Rossum35f75421998-10-13 23:51:13 +0000156 if debugger:
157 debugger.run(code, self.locals)
158 else:
159 exec code in self.locals
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000160 except SystemExit:
161 if tkMessageBox.askyesno(
162 "Exit?",
163 "Do you want to exit altogether?",
164 default="yes",
165 master=self.tkconsole.text):
166 raise
167 else:
168 self.showtraceback()
169 except:
170 self.showtraceback()
171 finally:
172 self.tkconsole.endexecuting()
173
174 def write(self, s):
175 # Override base class write
176 self.tkconsole.console.write(s)
177
178
Guido van Rossum5af7a721998-10-12 23:59:27 +0000179class PyShell(PyShellEditorWindow):
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000180
181 # Override classes
182 ColorDelegator = ModifiedColorDelegator
183 IOBinding = ModifiedIOBinding
184
Guido van Rossum5af7a721998-10-12 23:59:27 +0000185 # Override menu bar specs
186 menu_specs = PyShellEditorWindow.menu_specs[:]
187 menu_specs.insert(len(menu_specs)-1, ("debug", "Debug"))
188
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000189 # New class
190 from History import History
191
192 def __init__(self, flist=None):
193 self.interp = ModifiedInterpreter(self)
194 if flist is None:
195 root = Tk()
196 fixwordbreaks(root)
197 root.withdraw()
Guido van Rossum5af7a721998-10-12 23:59:27 +0000198 flist = PyShellFileList(root)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000199
Guido van Rossum5af7a721998-10-12 23:59:27 +0000200 PyShellEditorWindow.__init__(self, flist, None, None)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000201 self.config_colors()
202
203 import __builtin__
204 __builtin__.quit = __builtin__.exit = "To exit, type Ctrl-D."
205
206 self.auto.config(prefertabs=1)
207
208 text = self.text
209 text.bind("<<newline-and-indent>>", self.enter_callback)
210 text.bind("<<plain-newline-and-indent>>", self.linefeed_callback)
211 text.bind("<<interrupt-execution>>", self.cancel_callback)
212 text.bind("<<beginning-of-line>>", self.home_callback)
213 text.bind("<<end-of-file>>", self.eof_callback)
Guido van Rossum5af7a721998-10-12 23:59:27 +0000214 text.bind("<<goto-traceback-line>>", self.goto_traceback_line)
215 text.bind("<<open-stack-viewer>>", self.open_stack_viewer)
Guido van Rossum35f75421998-10-13 23:51:13 +0000216 text.bind("<<toggle-debugger>>", self.toggle_debugger)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000217
218 sys.stdout = PseudoFile(self, "stdout")
Guido van Rossum3f08d401998-10-13 15:21:41 +0000219 sys.stderr = PseudoFile(self, "stderr")
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000220 sys.stdin = self
221 self.console = PseudoFile(self, "console")
222
223 self.history = self.History(self.text)
224
225 tagdefs = {
226 ##"stdin": {"background": "yellow"},
227 "stdout": {"foreground": "blue"},
228 "stderr": {"foreground": "#007700"},
229 "console": {"foreground": "red"},
230 "ERROR": {"background": "#FF7777"},
231 None: {"foreground": "purple"}, # default
232 }
233
234 def config_colors(self):
235 for tag, cnf in self.tagdefs.items():
236 if cnf:
237 if not tag:
238 apply(self.text.configure, (), cnf)
239 else:
240 apply(self.text.tag_configure, (tag,), cnf)
241
242 reading = 0
243 executing = 0
244 canceled = 0
245 endoffile = 0
Guido van Rossum35f75421998-10-13 23:51:13 +0000246
247 def toggle_debugger(self, event=None):
248 if self.executing:
249 tkMessageBox.showerror("Don't debug now",
250 "You can only toggle the debugger when idle",
251 master=self.text)
252 return "break"
253 db = self.interp.getdebugger()
254 if db:
Guido van Rossum35e55da1998-10-14 03:43:05 +0000255 self.close_debugger()
256 else:
257 self.open_debugger()
258
259 def close_debugger(self):
260 db = self.interp.getdebugger()
261 if db:
262 self.interp.setdebugger(None)
Guido van Rossum35f75421998-10-13 23:51:13 +0000263 db.close()
264 self.resetoutput()
265 self.console.write("[DEBUG OFF]\n")
266 sys.ps1 = ">>> "
267 self.showprompt()
Guido van Rossum35e55da1998-10-14 03:43:05 +0000268
269 def open_debugger(self):
270 import Debugger
271 self.interp.setdebugger(Debugger.Debugger(self))
272 sys.ps1 = "[DEBUG ON]>>> "
273 self.showprompt()
274 self.top.tkraise()
275 self.text.focus_set()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000276
277 def beginexecuting(self):
278 # Helper for ModifiedInterpreter
279 self.resetoutput()
280 self.executing = 1
281 self._cancel_check = self.cancel_check
282 ##sys.settrace(self._cancel_check)
283
284 def endexecuting(self):
285 # Helper for ModifiedInterpreter
286 sys.settrace(None)
287 self.executing = 0
288 self.canceled = 0
289
290 def close(self):
291 # Extend base class method
292 if self.executing:
293 # XXX Need to ask a question here
294 if not tkMessageBox.askokcancel(
295 "Cancel?",
296 "The program is still running; do you want to cancel it?",
297 default="ok",
298 master=self.text):
299 return "cancel"
300 self.canceled = 1
301 if self.reading:
302 self.top.quit()
303 return "cancel"
Guido van Rossum5af7a721998-10-12 23:59:27 +0000304 reply = PyShellEditorWindow.close(self)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000305 if reply != "cancel":
Guido van Rossum5af7a721998-10-12 23:59:27 +0000306 self.flist.pyshell = None
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000307 # Restore std streams
308 sys.stdout = sys.__stdout__
309 sys.stderr = sys.__stderr__
310 sys.stdin = sys.__stdin__
311 # Break cycles
312 self.interp = None
313 self.console = None
314 return reply
315
316 def ispythonsource(self, filename):
317 # Override this so EditorWindow never removes the colorizer
318 return 1
319
320 def saved_change_hook(self):
321 # Override this to get the title right
322 title = "Python Shell"
323 if self.io.filename:
324 title = title + ": " + self.io.filename
325 if not self.undo.get_saved():
326 title = title + " *"
327 self.top.wm_title(title)
328
Guido van Rossum5af7a721998-10-12 23:59:27 +0000329 def begin(self):
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000330 self.resetoutput()
331 self.write("Python %s on %s\n%s\n" %
332 (sys.version, sys.platform, sys.copyright))
333 try:
334 sys.ps1
335 except AttributeError:
336 sys.ps1 = ">>> "
337 self.showprompt()
338 import Tkinter
339 Tkinter._default_root = None
Guido van Rossum5af7a721998-10-12 23:59:27 +0000340
341 def interact(self):
342 self.begin()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000343 self.top.mainloop()
344
345 def readline(self):
346 save = self.reading
347 try:
348 self.reading = 1
349 self.top.mainloop()
350 finally:
351 self.reading = save
352 line = self.text.get("iomark", "end-1c")
353 self.resetoutput()
354 if self.canceled:
355 self.canceled = 0
356 raise KeyboardInterrupt
357 if self.endoffile:
358 self.endoffile = 0
359 return ""
360 return line
361
362 def cancel_callback(self, event):
363 try:
364 if self.text.compare("sel.first", "!=", "sel.last"):
365 return # Active selection -- always use default binding
366 except:
367 pass
368 if not (self.executing or self.reading):
369 self.resetoutput()
370 self.write("KeyboardInterrupt\n")
371 self.showprompt()
372 return "break"
373 self.endoffile = 0
374 self.canceled = 1
375 if self.reading:
376 self.top.quit()
377 return "break"
378
379 def eof_callback(self, event):
380 if self.executing and not self.reading:
381 return # Let the default binding (delete next char) take over
382 if not (self.text.compare("iomark", "==", "insert") and
383 self.text.compare("insert", "==", "end-1c")):
384 return # Let the default binding (delete next char) take over
385 if not self.executing:
386## if not tkMessageBox.askokcancel(
387## "Exit?",
388## "Are you sure you want to exit?",
389## default="ok", master=self.text):
390## return "break"
391 self.resetoutput()
392 self.close()
393 else:
394 self.canceled = 0
395 self.endoffile = 1
396 self.top.quit()
397 return "break"
398
399 def home_callback(self, event):
400 if event.state != 0 and event.keysym == "Home":
401 return # <Modifier-Home>; fall back to class binding
402 if self.text.compare("iomark", "<=", "insert") and \
403 self.text.compare("insert linestart", "<=", "iomark"):
404 self.text.mark_set("insert", "iomark")
405 self.text.tag_remove("sel", "1.0", "end")
406 self.text.see("insert")
407 return "break"
408
409 def linefeed_callback(self, event):
410 # Insert a linefeed without entering anything (still autoindented)
411 if self.reading:
412 self.text.insert("insert", "\n")
413 self.text.see("insert")
414 else:
415 self.auto.autoindent(event)
416 return "break"
417
418 def enter_callback(self, event):
419 if self.executing and not self.reading:
420 return # Let the default binding (insert '\n') take over
421 # If some text is selected, recall the selection
Guido van Rossum4650df91998-10-13 14:41:27 +0000422 # (but only if this before the I/O mark)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000423 try:
424 sel = self.text.get("sel.first", "sel.last")
425 if sel:
Guido van Rossum4650df91998-10-13 14:41:27 +0000426 if self.text.compare("self.last", "<=", "iomark"):
427 self.recall(sel)
428 return "break"
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000429 except:
430 pass
431 # If we're strictly before the line containing iomark, recall
432 # the current line, less a leading prompt, less leading or
433 # trailing whitespace
434 if self.text.compare("insert", "<", "iomark linestart"):
Guido van Rossum4650df91998-10-13 14:41:27 +0000435 # Check if there's a relevant stdin range -- if so, use it
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000436 prev = self.text.tag_prevrange("stdin", "insert")
437 if prev and self.text.compare("insert", "<", prev[1]):
438 self.recall(self.text.get(prev[0], prev[1]))
439 return "break"
440 next = self.text.tag_nextrange("stdin", "insert")
441 if next and self.text.compare("insert lineend", ">=", next[0]):
442 self.recall(self.text.get(next[0], next[1]))
443 return "break"
444 # No stdin mark -- just get the current line
445 self.recall(self.text.get("insert linestart", "insert lineend"))
446 return "break"
Guido van Rossum4650df91998-10-13 14:41:27 +0000447 # If we're in the current input before its last line,
448 # insert a newline right at the insert point
449 if self.text.compare("insert", "<", "end-1c linestart"):
450 self.auto.autoindent(event)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000451 return "break"
Guido van Rossum4650df91998-10-13 14:41:27 +0000452 # We're in the last line; append a newline and submit it
453 self.text.mark_set("insert", "end-1c")
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000454 if self.reading:
455 self.text.insert("insert", "\n")
456 self.text.see("insert")
457 else:
458 self.auto.autoindent(event)
459 self.text.tag_add("stdin", "iomark", "end-1c")
460 self.text.update_idletasks()
461 if self.reading:
462 self.top.quit() # Break out of recursive mainloop() in raw_input()
463 else:
464 self.runit()
465 return "break"
466
467 def recall(self, s):
468 if self.history:
469 self.history.recall(s)
470
471 def runit(self):
472 line = self.text.get("iomark", "end-1c")
473 # Strip off last newline and surrounding whitespace.
474 # (To allow you to hit return twice to end a statement.)
475 i = len(line)
476 while i > 0 and line[i-1] in " \t":
477 i = i-1
478 if i > 0 and line[i-1] == "\n":
479 i = i-1
480 while i > 0 and line[i-1] in " \t":
481 i = i-1
482 line = line[:i]
483 more = self.interp.runsource(line)
484 if not more:
485 self.showprompt()
486
487 def cancel_check(self, frame, what, args,
488 dooneevent=tkinter.dooneevent,
489 dontwait=tkinter.DONT_WAIT):
490 # Hack -- use the debugger hooks to be able to handle events
491 # and interrupt execution at any time.
492 # This slows execution down quite a bit, so you may want to
493 # disable this (by not calling settrace() in runcode() above)
494 # for full-bore (uninterruptable) speed.
495 # XXX This should become a user option.
496 if self.canceled:
497 return
498 dooneevent(dontwait)
499 if self.canceled:
500 self.canceled = 0
501 raise KeyboardInterrupt
502 return self._cancel_check
Guido van Rossum5af7a721998-10-12 23:59:27 +0000503
504 file_line_pats = [
505 r'File "([^"]*)", line (\d+)',
506 r'([^\s]+)\((\d+)\)',
507 r'([^\s]+):\s*(\d+):',
508 ]
509
510 file_line_progs = None
511
512 def goto_traceback_line(self, event=None):
513 if self.file_line_progs is None:
514 l = []
515 for pat in self.file_line_pats:
516 l.append(re.compile(pat))
517 self.file_line_progs = l
518 # x, y = self.event.x, self.event.y
519 # self.text.mark_set("insert", "@%d,%d" % (x, y))
520 line = self.text.get("insert linestart", "insert lineend")
521 for prog in self.file_line_progs:
522 m = prog.search(line)
523 if m:
524 break
525 else:
526 tkMessageBox.showerror("No traceback line",
527 "The line you point at doesn't look "
528 "like an error message.",
529 master=self.text)
530 return
531 filename, lineno = m.group(1, 2)
532 try:
533 f = open(filename, "r")
534 f.close()
535 except IOError, msg:
536 self.text.bell()
537 return
538 edit = self.flist.open(filename)
539 try:
540 lineno = int(lineno)
541 except ValueError, msg:
542 self.text.bell()
543 return
544 edit.gotoline(lineno)
545
546 def open_stack_viewer(self, event=None):
547 try:
548 sys.last_traceback
549 except:
550 tkMessageBox.showerror("No stack trace",
551 "There is no stack trace yet.\n"
552 "(sys.last_traceback is not defined)",
553 master=self.text)
554 return
555 from StackViewer import StackViewer
556 sv = StackViewer(self.root, self.flist)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000557
558 def showprompt(self):
559 self.resetoutput()
560 try:
561 s = str(sys.ps1)
562 except:
563 s = ""
564 self.console.write(s)
565 self.text.mark_set("insert", "end-1c")
566
567 def resetoutput(self):
568 source = self.text.get("iomark", "end-1c")
569 if self.history:
570 self.history.history_store(source)
571 if self.text.get("end-2c") != "\n":
572 self.text.insert("end-1c", "\n")
573 self.text.mark_set("iomark", "end-1c")
574 sys.stdout.softspace = 0
575
576 def write(self, s):
577 # Overrides base class write
578 self.console.write(s)
579
580class PseudoFile:
581
582 def __init__(self, interp, tags):
583 self.interp = interp
584 self.text = interp.text
585 self.tags = tags
586
587 def write(self, s):
588 self.text.mark_gravity("iomark", "right")
589 self.text.insert("iomark", str(s), self.tags)
590 self.text.mark_gravity("iomark", "left")
591 self.text.see("iomark")
592 self.text.update()
593 if self.interp.canceled:
594 self.interp.canceled = 0
595 raise KeyboardInterrupt
596
597 def writelines(self, l):
598 map(self.write, l)
599
600
601def main():
602 global flist, root
603 root = Tk()
604 fixwordbreaks(root)
605 root.withdraw()
Guido van Rossum5af7a721998-10-12 23:59:27 +0000606 flist = PyShellFileList(root)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000607 if sys.argv[1:]:
608 for filename in sys.argv[1:]:
609 flist.open(filename)
610 t = PyShell(flist)
Guido van Rossum5af7a721998-10-12 23:59:27 +0000611 flist.pyshell = t
612 t.begin()
613 root.mainloop()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000614
615if __name__ == "__main__":
616 main()