Merged release21-maint changes.
[python/dscho.git] / Tools / idle / PyShell.py
blob03b468424d0aaf90c7a07505d142067eecbaea3a
1 #! /usr/bin/env python
3 import os
4 import sys
5 import string
6 import getopt
7 import re
8 import warnings
10 import linecache
11 from code import InteractiveInterpreter
13 from Tkinter import *
14 import tkMessageBox
16 from EditorWindow import EditorWindow, fixwordbreaks
17 from FileList import FileList
18 from ColorDelegator import ColorDelegator
19 from UndoDelegator import UndoDelegator
20 from OutputWindow import OutputWindow
21 from IdleConf import idleconf
22 import idlever
24 # We need to patch linecache.checkcache, because we don't want it
25 # to throw away our <pyshell#...> entries.
26 # Rather than repeating its code here, we save those entries,
27 # then call the original function, and then restore the saved entries.
28 def linecache_checkcache(orig_checkcache=linecache.checkcache):
29 cache = linecache.cache
30 save = {}
31 for filename in cache.keys():
32 if filename[:1] + filename[-1:] == '<>':
33 save[filename] = cache[filename]
34 orig_checkcache()
35 cache.update(save)
36 linecache.checkcache = linecache_checkcache
39 IDENTCHARS = string.ascii_letters + string.digits + "_"
42 # Note: <<newline-and-indent>> event is defined in AutoIndent.py
44 #$ event <<plain-newline-and-indent>>
45 #$ win <Control-j>
46 #$ unix <Control-j>
48 #$ event <<beginning-of-line>>
49 #$ win <Control-a>
50 #$ win <Home>
51 #$ unix <Control-a>
52 #$ unix <Home>
54 #$ event <<history-next>>
55 #$ win <Alt-n>
56 #$ unix <Alt-n>
58 #$ event <<history-previous>>
59 #$ win <Alt-p>
60 #$ unix <Alt-p>
62 #$ event <<interrupt-execution>>
63 #$ win <Control-c>
64 #$ unix <Control-c>
66 #$ event <<end-of-file>>
67 #$ win <Control-d>
68 #$ unix <Control-d>
70 #$ event <<open-stack-viewer>>
72 #$ event <<toggle-debugger>>
75 class PyShellEditorWindow(EditorWindow):
77 # Regular text edit window when a shell is present
78 # XXX ought to merge with regular editor window
80 def __init__(self, *args):
81 apply(EditorWindow.__init__, (self,) + args)
82 self.text.bind("<<set-breakpoint-here>>", self.set_breakpoint_here)
83 self.text.bind("<<open-python-shell>>", self.flist.open_shell)
85 rmenu_specs = [
86 ("Set breakpoint here", "<<set-breakpoint-here>>"),
89 def set_breakpoint_here(self, event=None):
90 if not self.flist.pyshell or not self.flist.pyshell.interp.debugger:
91 self.text.bell()
92 return
93 self.flist.pyshell.interp.debugger.set_breakpoint_here(self)
96 class PyShellFileList(FileList):
98 # File list when a shell is present
100 EditorWindow = PyShellEditorWindow
102 pyshell = None
104 def open_shell(self, event=None):
105 if self.pyshell:
106 self.pyshell.wakeup()
107 else:
108 self.pyshell = PyShell(self)
109 self.pyshell.begin()
110 return self.pyshell
113 class ModifiedColorDelegator(ColorDelegator):
115 # Colorizer for the shell window itself
117 def recolorize_main(self):
118 self.tag_remove("TODO", "1.0", "iomark")
119 self.tag_add("SYNC", "1.0", "iomark")
120 ColorDelegator.recolorize_main(self)
122 tagdefs = ColorDelegator.tagdefs.copy()
123 cconf = idleconf.getsection('Colors')
125 tagdefs.update({
126 "stdin": cconf.getcolor("stdin"),
127 "stdout": cconf.getcolor("stdout"),
128 "stderr": cconf.getcolor("stderr"),
129 "console": cconf.getcolor("console"),
130 "ERROR": cconf.getcolor("ERROR"),
131 None: cconf.getcolor("normal"),
135 class ModifiedUndoDelegator(UndoDelegator):
137 # Forbid insert/delete before the I/O mark
139 def insert(self, index, chars, tags=None):
140 try:
141 if self.delegate.compare(index, "<", "iomark"):
142 self.delegate.bell()
143 return
144 except TclError:
145 pass
146 UndoDelegator.insert(self, index, chars, tags)
148 def delete(self, index1, index2=None):
149 try:
150 if self.delegate.compare(index1, "<", "iomark"):
151 self.delegate.bell()
152 return
153 except TclError:
154 pass
155 UndoDelegator.delete(self, index1, index2)
157 class ModifiedInterpreter(InteractiveInterpreter):
159 def __init__(self, tkconsole):
160 self.tkconsole = tkconsole
161 locals = sys.modules['__main__'].__dict__
162 InteractiveInterpreter.__init__(self, locals=locals)
163 self.save_warnings_filters = None
165 gid = 0
167 def execsource(self, source):
168 # Like runsource() but assumes complete exec source
169 filename = self.stuffsource(source)
170 self.execfile(filename, source)
172 def execfile(self, filename, source=None):
173 # Execute an existing file
174 if source is None:
175 source = open(filename, "r").read()
176 try:
177 code = compile(source, filename, "exec")
178 except (OverflowError, SyntaxError):
179 self.tkconsole.resetoutput()
180 InteractiveInterpreter.showsyntaxerror(self, filename)
181 else:
182 self.runcode(code)
184 def runsource(self, source):
185 # Extend base class to stuff the source in the line cache first
186 filename = self.stuffsource(source)
187 self.more = 0
188 self.save_warnings_filters = warnings.filters[:]
189 warnings.filterwarnings(action="error", category=SyntaxWarning)
190 try:
191 return InteractiveInterpreter.runsource(self, source, filename)
192 finally:
193 if self.save_warnings_filters is not None:
194 warnings.filters[:] = self.save_warnings_filters
195 self.save_warnings_filters = None
197 def stuffsource(self, source):
198 # Stuff source in the filename cache
199 filename = "<pyshell#%d>" % self.gid
200 self.gid = self.gid + 1
201 lines = string.split(source, "\n")
202 linecache.cache[filename] = len(source)+1, 0, lines, filename
203 return filename
205 def showsyntaxerror(self, filename=None):
206 # Extend base class to color the offending position
207 # (instead of printing it and pointing at it with a caret)
208 text = self.tkconsole.text
209 stuff = self.unpackerror()
210 if not stuff:
211 self.tkconsole.resetoutput()
212 InteractiveInterpreter.showsyntaxerror(self, filename)
213 return
214 msg, lineno, offset, line = stuff
215 if lineno == 1:
216 pos = "iomark + %d chars" % (offset-1)
217 else:
218 pos = "iomark linestart + %d lines + %d chars" % (lineno-1,
219 offset-1)
220 text.tag_add("ERROR", pos)
221 text.see(pos)
222 char = text.get(pos)
223 if char and char in IDENTCHARS:
224 text.tag_add("ERROR", pos + " wordstart", pos)
225 self.tkconsole.resetoutput()
226 self.write("SyntaxError: %s\n" % str(msg))
228 def unpackerror(self):
229 type, value, tb = sys.exc_info()
230 ok = type is SyntaxError
231 if ok:
232 try:
233 msg, (dummy_filename, lineno, offset, line) = value
234 except:
235 ok = 0
236 if ok:
237 return msg, lineno, offset, line
238 else:
239 return None
241 def showtraceback(self):
242 # Extend base class method to reset output properly
243 text = self.tkconsole.text
244 self.tkconsole.resetoutput()
245 self.checklinecache()
246 InteractiveInterpreter.showtraceback(self)
248 def checklinecache(self):
249 c = linecache.cache
250 for key in c.keys():
251 if key[:1] + key[-1:] != "<>":
252 del c[key]
254 debugger = None
256 def setdebugger(self, debugger):
257 self.debugger = debugger
259 def getdebugger(self):
260 return self.debugger
262 def runcode(self, code):
263 # Override base class method
264 if self.save_warnings_filters is not None:
265 warnings.filters[:] = self.save_warnings_filters
266 self.save_warnings_filters = None
267 debugger = self.debugger
268 try:
269 self.tkconsole.beginexecuting()
270 try:
271 if debugger:
272 debugger.run(code, self.locals)
273 else:
274 exec code in self.locals
275 except SystemExit:
276 if tkMessageBox.askyesno(
277 "Exit?",
278 "Do you want to exit altogether?",
279 default="yes",
280 master=self.tkconsole.text):
281 raise
282 else:
283 self.showtraceback()
284 if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
285 self.tkconsole.open_stack_viewer()
286 except:
287 self.showtraceback()
288 if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
289 self.tkconsole.open_stack_viewer()
291 finally:
292 self.tkconsole.endexecuting()
294 def write(self, s):
295 # Override base class write
296 self.tkconsole.console.write(s)
299 class PyShell(OutputWindow):
301 shell_title = "Python Shell"
303 # Override classes
304 ColorDelegator = ModifiedColorDelegator
305 UndoDelegator = ModifiedUndoDelegator
307 # Override menu bar specs
308 menu_specs = PyShellEditorWindow.menu_specs[:]
309 menu_specs.insert(len(menu_specs)-2, ("debug", "_Debug"))
311 # New classes
312 from IdleHistory import History
314 def __init__(self, flist=None):
315 self.interp = ModifiedInterpreter(self)
316 if flist is None:
317 root = Tk()
318 fixwordbreaks(root)
319 root.withdraw()
320 flist = PyShellFileList(root)
322 OutputWindow.__init__(self, flist, None, None)
324 import __builtin__
325 __builtin__.quit = __builtin__.exit = "To exit, type Ctrl-D."
327 self.auto = self.extensions["AutoIndent"] # Required extension
328 self.auto.config(usetabs=1, indentwidth=8, context_use_ps1=1)
330 text = self.text
331 text.configure(wrap="char")
332 text.bind("<<newline-and-indent>>", self.enter_callback)
333 text.bind("<<plain-newline-and-indent>>", self.linefeed_callback)
334 text.bind("<<interrupt-execution>>", self.cancel_callback)
335 text.bind("<<beginning-of-line>>", self.home_callback)
336 text.bind("<<end-of-file>>", self.eof_callback)
337 text.bind("<<open-stack-viewer>>", self.open_stack_viewer)
338 text.bind("<<toggle-debugger>>", self.toggle_debugger)
339 text.bind("<<open-python-shell>>", self.flist.open_shell)
340 text.bind("<<toggle-jit-stack-viewer>>", self.toggle_jit_stack_viewer)
342 self.save_stdout = sys.stdout
343 self.save_stderr = sys.stderr
344 self.save_stdin = sys.stdin
345 sys.stdout = PseudoFile(self, "stdout")
346 sys.stderr = PseudoFile(self, "stderr")
347 sys.stdin = self
348 self.console = PseudoFile(self, "console")
350 self.history = self.History(self.text)
352 reading = 0
353 executing = 0
354 canceled = 0
355 endoffile = 0
357 def toggle_debugger(self, event=None):
358 if self.executing:
359 tkMessageBox.showerror("Don't debug now",
360 "You can only toggle the debugger when idle",
361 master=self.text)
362 self.set_debugger_indicator()
363 return "break"
364 else:
365 db = self.interp.getdebugger()
366 if db:
367 self.close_debugger()
368 else:
369 self.open_debugger()
371 def set_debugger_indicator(self):
372 db = self.interp.getdebugger()
373 self.setvar("<<toggle-debugger>>", not not db)
375 def toggle_jit_stack_viewer( self, event=None):
376 pass # All we need is the variable
378 def close_debugger(self):
379 db = self.interp.getdebugger()
380 if db:
381 self.interp.setdebugger(None)
382 db.close()
383 self.resetoutput()
384 self.console.write("[DEBUG OFF]\n")
385 sys.ps1 = ">>> "
386 self.showprompt()
387 self.set_debugger_indicator()
389 def open_debugger(self):
390 import Debugger
391 self.interp.setdebugger(Debugger.Debugger(self))
392 sys.ps1 = "[DEBUG ON]\n>>> "
393 self.showprompt()
394 self.set_debugger_indicator()
396 def beginexecuting(self):
397 # Helper for ModifiedInterpreter
398 self.resetoutput()
399 self.executing = 1
400 ##self._cancel_check = self.cancel_check
401 ##sys.settrace(self._cancel_check)
403 def endexecuting(self):
404 # Helper for ModifiedInterpreter
405 ##sys.settrace(None)
406 ##self._cancel_check = None
407 self.executing = 0
408 self.canceled = 0
410 def close(self):
411 # Extend base class method
412 if self.executing:
413 # XXX Need to ask a question here
414 if not tkMessageBox.askokcancel(
415 "Kill?",
416 "The program is still running; do you want to kill it?",
417 default="ok",
418 master=self.text):
419 return "cancel"
420 self.canceled = 1
421 if self.reading:
422 self.top.quit()
423 return "cancel"
424 return OutputWindow.close(self)
426 def _close(self):
427 self.close_debugger()
428 # Restore std streams
429 sys.stdout = self.save_stdout
430 sys.stderr = self.save_stderr
431 sys.stdin = self.save_stdin
432 # Break cycles
433 self.interp = None
434 self.console = None
435 self.auto = None
436 self.flist.pyshell = None
437 self.history = None
438 OutputWindow._close(self) # Really EditorWindow._close
440 def ispythonsource(self, filename):
441 # Override this so EditorWindow never removes the colorizer
442 return 1
444 def short_title(self):
445 return self.shell_title
447 COPYRIGHT = \
448 'Type "copyright", "credits" or "license" for more information.'
450 def begin(self):
451 self.resetoutput()
452 self.write("Python %s on %s\n%s\nIDLE %s -- press F1 for help\n" %
453 (sys.version, sys.platform, self.COPYRIGHT,
454 idlever.IDLE_VERSION))
455 try:
456 sys.ps1
457 except AttributeError:
458 sys.ps1 = ">>> "
459 self.showprompt()
460 import Tkinter
461 Tkinter._default_root = None
463 def interact(self):
464 self.begin()
465 self.top.mainloop()
467 def readline(self):
468 save = self.reading
469 try:
470 self.reading = 1
471 self.top.mainloop()
472 finally:
473 self.reading = save
474 line = self.text.get("iomark", "end-1c")
475 self.resetoutput()
476 if self.canceled:
477 self.canceled = 0
478 raise KeyboardInterrupt
479 if self.endoffile:
480 self.endoffile = 0
481 return ""
482 return line
484 def isatty(self):
485 return 1
487 def cancel_callback(self, event):
488 try:
489 if self.text.compare("sel.first", "!=", "sel.last"):
490 return # Active selection -- always use default binding
491 except:
492 pass
493 if not (self.executing or self.reading):
494 self.resetoutput()
495 self.write("KeyboardInterrupt\n")
496 self.showprompt()
497 return "break"
498 self.endoffile = 0
499 self.canceled = 1
500 if self.reading:
501 self.top.quit()
502 return "break"
504 def eof_callback(self, event):
505 if self.executing and not self.reading:
506 return # Let the default binding (delete next char) take over
507 if not (self.text.compare("iomark", "==", "insert") and
508 self.text.compare("insert", "==", "end-1c")):
509 return # Let the default binding (delete next char) take over
510 if not self.executing:
511 ## if not tkMessageBox.askokcancel(
512 ## "Exit?",
513 ## "Are you sure you want to exit?",
514 ## default="ok", master=self.text):
515 ## return "break"
516 self.resetoutput()
517 self.close()
518 else:
519 self.canceled = 0
520 self.endoffile = 1
521 self.top.quit()
522 return "break"
524 def home_callback(self, event):
525 if event.state != 0 and event.keysym == "Home":
526 return # <Modifier-Home>; fall back to class binding
527 if self.text.compare("iomark", "<=", "insert") and \
528 self.text.compare("insert linestart", "<=", "iomark"):
529 self.text.mark_set("insert", "iomark")
530 self.text.tag_remove("sel", "1.0", "end")
531 self.text.see("insert")
532 return "break"
534 def linefeed_callback(self, event):
535 # Insert a linefeed without entering anything (still autoindented)
536 if self.reading:
537 self.text.insert("insert", "\n")
538 self.text.see("insert")
539 else:
540 self.auto.auto_indent(event)
541 return "break"
543 def enter_callback(self, event):
544 if self.executing and not self.reading:
545 return # Let the default binding (insert '\n') take over
546 # If some text is selected, recall the selection
547 # (but only if this before the I/O mark)
548 try:
549 sel = self.text.get("sel.first", "sel.last")
550 if sel:
551 if self.text.compare("sel.last", "<=", "iomark"):
552 self.recall(sel)
553 return "break"
554 except:
555 pass
556 # If we're strictly before the line containing iomark, recall
557 # the current line, less a leading prompt, less leading or
558 # trailing whitespace
559 if self.text.compare("insert", "<", "iomark linestart"):
560 # Check if there's a relevant stdin range -- if so, use it
561 prev = self.text.tag_prevrange("stdin", "insert")
562 if prev and self.text.compare("insert", "<", prev[1]):
563 self.recall(self.text.get(prev[0], prev[1]))
564 return "break"
565 next = self.text.tag_nextrange("stdin", "insert")
566 if next and self.text.compare("insert lineend", ">=", next[0]):
567 self.recall(self.text.get(next[0], next[1]))
568 return "break"
569 # No stdin mark -- just get the current line
570 self.recall(self.text.get("insert linestart", "insert lineend"))
571 return "break"
572 # If we're in the current input and there's only whitespace
573 # beyond the cursor, erase that whitespace first
574 s = self.text.get("insert", "end-1c")
575 if s and not string.strip(s):
576 self.text.delete("insert", "end-1c")
577 # If we're in the current input before its last line,
578 # insert a newline right at the insert point
579 if self.text.compare("insert", "<", "end-1c linestart"):
580 self.auto.auto_indent(event)
581 return "break"
582 # We're in the last line; append a newline and submit it
583 self.text.mark_set("insert", "end-1c")
584 if self.reading:
585 self.text.insert("insert", "\n")
586 self.text.see("insert")
587 else:
588 self.auto.auto_indent(event)
589 self.text.tag_add("stdin", "iomark", "end-1c")
590 self.text.update_idletasks()
591 if self.reading:
592 self.top.quit() # Break out of recursive mainloop() in raw_input()
593 else:
594 self.runit()
595 return "break"
597 def recall(self, s):
598 if self.history:
599 self.history.recall(s)
601 def runit(self):
602 line = self.text.get("iomark", "end-1c")
603 # Strip off last newline and surrounding whitespace.
604 # (To allow you to hit return twice to end a statement.)
605 i = len(line)
606 while i > 0 and line[i-1] in " \t":
607 i = i-1
608 if i > 0 and line[i-1] == "\n":
609 i = i-1
610 while i > 0 and line[i-1] in " \t":
611 i = i-1
612 line = line[:i]
613 more = self.interp.runsource(line)
614 if not more:
615 self.showprompt()
617 def cancel_check(self, frame, what, args,
618 dooneevent=tkinter.dooneevent,
619 dontwait=tkinter.DONT_WAIT):
620 # Hack -- use the debugger hooks to be able to handle events
621 # and interrupt execution at any time.
622 # This slows execution down quite a bit, so you may want to
623 # disable this (by not calling settrace() in runcode() above)
624 # for full-bore (uninterruptable) speed.
625 # XXX This should become a user option.
626 if self.canceled:
627 return
628 dooneevent(dontwait)
629 if self.canceled:
630 self.canceled = 0
631 raise KeyboardInterrupt
632 return self._cancel_check
634 def open_stack_viewer(self, event=None):
635 try:
636 sys.last_traceback
637 except:
638 tkMessageBox.showerror("No stack trace",
639 "There is no stack trace yet.\n"
640 "(sys.last_traceback is not defined)",
641 master=self.text)
642 return
643 from StackViewer import StackBrowser
644 sv = StackBrowser(self.root, self.flist)
646 def showprompt(self):
647 self.resetoutput()
648 try:
649 s = str(sys.ps1)
650 except:
651 s = ""
652 self.console.write(s)
653 self.text.mark_set("insert", "end-1c")
655 def resetoutput(self):
656 source = self.text.get("iomark", "end-1c")
657 if self.history:
658 self.history.history_store(source)
659 if self.text.get("end-2c") != "\n":
660 self.text.insert("end-1c", "\n")
661 self.text.mark_set("iomark", "end-1c")
662 sys.stdout.softspace = 0
664 def write(self, s, tags=()):
665 self.text.mark_gravity("iomark", "right")
666 OutputWindow.write(self, s, tags, "iomark")
667 self.text.mark_gravity("iomark", "left")
668 if self.canceled:
669 self.canceled = 0
670 raise KeyboardInterrupt
672 class PseudoFile:
674 def __init__(self, shell, tags):
675 self.shell = shell
676 self.tags = tags
678 def write(self, s):
679 self.shell.write(s, self.tags)
681 def writelines(self, l):
682 map(self.write, l)
684 def flush(self):
685 pass
687 def isatty(self):
688 return 1
691 usage_msg = """\
692 usage: idle.py [-c command] [-d] [-e] [-s] [-t title] [arg] ...
694 -c command run this command
695 -d enable debugger
696 -e edit mode; arguments are files to be edited
697 -s run $IDLESTARTUP or $PYTHONSTARTUP before anything else
698 -t title set title of shell window
700 When neither -c nor -e is used, and there are arguments, and the first
701 argument is not '-', the first argument is run as a script. Remaining
702 arguments are arguments to the script or to the command run by -c.
705 def main():
706 cmd = None
707 edit = 0
708 debug = 0
709 startup = 0
711 try:
712 opts, args = getopt.getopt(sys.argv[1:], "c:deist:")
713 except getopt.error, msg:
714 sys.stderr.write("Error: %s\n" % str(msg))
715 sys.stderr.write(usage_msg)
716 sys.exit(2)
718 for o, a in opts:
719 if o == '-c':
720 cmd = a
721 if o == '-d':
722 debug = 1
723 if o == '-e':
724 edit = 1
725 if o == '-s':
726 startup = 1
727 if o == '-t':
728 PyShell.shell_title = a
730 for i in range(len(sys.path)):
731 sys.path[i] = os.path.abspath(sys.path[i])
733 pathx = []
734 if edit:
735 for filename in args:
736 pathx.append(os.path.dirname(filename))
737 elif args and args[0] != "-":
738 pathx.append(os.path.dirname(args[0]))
739 else:
740 pathx.append(os.curdir)
741 for dir in pathx:
742 dir = os.path.abspath(dir)
743 if not dir in sys.path:
744 sys.path.insert(0, dir)
746 global flist, root
747 root = Tk(className="Idle")
748 fixwordbreaks(root)
749 root.withdraw()
750 flist = PyShellFileList(root)
752 if edit:
753 for filename in args:
754 flist.open(filename)
755 else:
756 if cmd:
757 sys.argv = ["-c"] + args
758 else:
759 sys.argv = args or [""]
762 shell = PyShell(flist)
763 interp = shell.interp
764 flist.pyshell = shell
766 if startup:
767 filename = os.environ.get("IDLESTARTUP") or \
768 os.environ.get("PYTHONSTARTUP")
769 if filename and os.path.isfile(filename):
770 interp.execfile(filename)
772 if debug:
773 shell.open_debugger()
774 if cmd:
775 interp.execsource(cmd)
776 elif not edit and args and args[0] != "-":
777 interp.execfile(args[0])
779 shell.begin()
780 root.mainloop()
781 root.destroy()
784 if __name__ == "__main__":
785 main()