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