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