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