PyShell.py revision 8dcdb77132563c734c228e815498c47e487f95cf
1#! /usr/bin/env python
2
3import os
4import sys
5import string
6import getopt
7import re
8import socket
9import time
10import warnings
11import traceback
12
13import linecache
14from code import InteractiveInterpreter
15
16from Tkinter import *
17import tkMessageBox
18
19from EditorWindow import EditorWindow, fixwordbreaks
20from FileList import FileList
21from ColorDelegator import ColorDelegator
22from UndoDelegator import UndoDelegator
23from OutputWindow import OutputWindow
24from configHandler import idleConf
25import idlever
26
27import rpc
28import RemoteDebugger
29
30# XX hardwire this for now, remove later  KBK 09Jun02
31use_subprocess = 1 # Set to 1 to spawn subprocess for command execution
32
33# Change warnings module to write to sys.__stderr__
34try:
35    import warnings
36except ImportError:
37    pass
38else:
39    def idle_showwarning(message, category, filename, lineno):
40        file = sys.__stderr__
41        file.write(warnings.formatwarning(message, category, filename, lineno))
42    warnings.showwarning = idle_showwarning
43
44# We need to patch linecache.checkcache, because we don't want it
45# to throw away our <pyshell#...> entries.
46# Rather than repeating its code here, we save those entries,
47# then call the original function, and then restore the saved entries.
48def linecache_checkcache(orig_checkcache=linecache.checkcache):
49    cache = linecache.cache
50    save = {}
51    for filename in cache.keys():
52        if filename[:1] + filename[-1:] == '<>':
53            save[filename] = cache[filename]
54    orig_checkcache()
55    cache.update(save)
56linecache.checkcache = linecache_checkcache
57
58
59# Note: <<newline-and-indent>> event is defined in AutoIndent.py
60
61#$ event <<plain-newline-and-indent>>
62#$ win <Control-j>
63#$ unix <Control-j>
64
65#$ event <<beginning-of-line>>
66#$ win <Control-a>
67#$ win <Home>
68#$ unix <Control-a>
69#$ unix <Home>
70
71#$ event <<history-next>>
72#$ win <Alt-n>
73#$ unix <Alt-n>
74
75#$ event <<history-previous>>
76#$ win <Alt-p>
77#$ unix <Alt-p>
78
79#$ event <<interrupt-execution>>
80#$ win <Control-c>
81#$ unix <Control-c>
82
83#$ event <<end-of-file>>
84#$ win <Control-d>
85#$ unix <Control-d>
86
87#$ event <<open-stack-viewer>>
88
89#$ event <<toggle-debugger>>
90
91
92class PyShellEditorWindow(EditorWindow):
93    "Regular text edit window when a shell is present"
94    # XXX ought to merge with regular editor window
95
96    def __init__(self, *args):
97        apply(EditorWindow.__init__, (self,) + args)
98        self.text.bind("<<set-breakpoint-here>>", self.set_breakpoint_here)
99        self.text.bind("<<clear-breakpoint-here>>",
100                       self.clear_breakpoint_here)
101        self.text.bind("<<open-python-shell>>", self.flist.open_shell)
102
103    rmenu_specs = [
104        ("Set Breakpoint", "<<set-breakpoint-here>>"),
105        ("Clear Breakpoint", "<<clear-breakpoint-here>>")
106    ]
107
108    def set_breakpoint_here(self, event=None):
109        if not self.flist.pyshell or not self.flist.pyshell.interp.debugger:
110            self.text.bell()
111            return
112        self.flist.pyshell.interp.debugger.set_breakpoint_here(self)
113
114    def clear_breakpoint_here(self, event=None):
115        if not self.flist.pyshell or not self.flist.pyshell.interp.debugger:
116            self.text.bell()
117            return
118        self.flist.pyshell.interp.debugger.clear_breakpoint_here(self)
119
120
121class PyShellFileList(FileList):
122    "Extend base class: file list when a shell is present"
123
124    EditorWindow = PyShellEditorWindow
125
126    pyshell = None
127
128    def open_shell(self, event=None):
129        if self.pyshell:
130            self.pyshell.wakeup()
131        else:
132            self.pyshell = PyShell(self)
133            self.pyshell.begin()
134        return self.pyshell
135
136
137class ModifiedColorDelegator(ColorDelegator):
138    "Extend base class: colorizer for the shell window itself"
139
140    def __init__(self):
141        ColorDelegator.__init__(self)
142        self.LoadTagDefs()
143
144    def recolorize_main(self):
145        self.tag_remove("TODO", "1.0", "iomark")
146        self.tag_add("SYNC", "1.0", "iomark")
147        ColorDelegator.recolorize_main(self)
148
149    def LoadTagDefs(self):
150        ColorDelegator.LoadTagDefs(self)
151        theme = idleConf.GetOption('main','Theme','name')
152        self.tagdefs.update({
153            "stdin": {'background':None,'foreground':None},
154            "stdout": idleConf.GetHighlight(theme, "stdout"),
155            "stderr": idleConf.GetHighlight(theme, "stderr"),
156            "console": idleConf.GetHighlight(theme, "console"),
157            "ERROR": idleConf.GetHighlight(theme, "error"),
158            None: idleConf.GetHighlight(theme, "normal"),
159        })
160
161class ModifiedUndoDelegator(UndoDelegator):
162    "Extend base class: forbid insert/delete before the I/O mark"
163
164    def insert(self, index, chars, tags=None):
165        try:
166            if self.delegate.compare(index, "<", "iomark"):
167                self.delegate.bell()
168                return
169        except TclError:
170            pass
171        UndoDelegator.insert(self, index, chars, tags)
172
173    def delete(self, index1, index2=None):
174        try:
175            if self.delegate.compare(index1, "<", "iomark"):
176                self.delegate.bell()
177                return
178        except TclError:
179            pass
180        UndoDelegator.delete(self, index1, index2)
181
182class ModifiedInterpreter(InteractiveInterpreter):
183
184    def __init__(self, tkconsole):
185        self.tkconsole = tkconsole
186        locals = sys.modules['__main__'].__dict__
187        InteractiveInterpreter.__init__(self, locals=locals)
188        self.save_warnings_filters = None
189
190    rpcclt = None
191    rpcpid = None
192
193    def spawn_subprocess(self):
194        port = 8833
195        addr = ("localhost", port)
196        # Spawn the Python execution "server"
197        w = ['-W' + s for s in sys.warnoptions]
198        args = [sys.executable] + w + ["-c", "__import__('run').main()",
199                                       str(port)]
200        self.rpcpid = os.spawnv(os.P_NOWAIT, args[0], args)
201        # Idle starts listening for connection on localhost
202        for i in range(6):
203            time.sleep(i)
204            try:
205                self.rpcclt = rpc.RPCClient(addr)
206                break
207            except socket.error, err:
208                if i < 3:
209                    print>>sys.__stderr__, ". ",
210                else:
211                    print>>sys.__stderr__,"\nIdle socket error: " + err[1]\
212                                                    + ", retrying..."
213        else:
214            display_port_binding_error()
215            return
216        # Accept the connection from the Python execution server
217        self.rpcclt.accept()
218        self.rpcclt.register("stdin", self.tkconsole)
219        self.rpcclt.register("stdout", self.tkconsole.stdout)
220        self.rpcclt.register("stderr", self.tkconsole.stderr)
221        self.rpcclt.register("flist", self.tkconsole.flist)
222        self.poll_subprocess()
223
224    active_seq = None
225
226    def poll_subprocess(self):
227        clt = self.rpcclt
228        if clt is None:
229            return
230        response = clt.pollresponse(self.active_seq)
231        self.tkconsole.text.after(50, self.poll_subprocess)
232        if response:
233            self.tkconsole.resetoutput()
234            self.active_seq = None
235            how, what = response
236            file = self.tkconsole.console
237            if how == "OK":
238                if what is not None:
239                    print >>file, `what`
240            elif how == "EXCEPTION":
241                mod, name, args, tb = what
242                print >>file, 'Traceback (most recent call last):'
243                while tb and tb[0][0] in ("run.py", "rpc.py"):
244                    del tb[0]
245                while tb and tb[-1][0] in ("run.py", "rpc.py"):
246                    del tb[-1]
247                for i in range(len(tb)):
248                    fn, ln, nm, line = tb[i]
249                    if not line and fn.startswith("<pyshell#"):
250                        line = linecache.getline(fn, ln)
251                        tb[i] = fn, ln, nm, line
252                traceback.print_list(tb, file=file)
253                if mod and mod != "exceptions":
254                    name = mod + "." + name
255                print >>file, name + ":", " ".join(map(str, args))
256                if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
257                    self.remote_stack_viewer()
258            elif how == "ERROR":
259                print >>sys.__stderr__, "Oops:", how, what
260                print >>file, "Oops:", how, what
261            self.tkconsole.endexecuting()
262
263    def kill_subprocess(self):
264        clt = self.rpcclt
265        self.rpcclt = None
266        if clt is not None:
267            clt.close()
268
269    def remote_stack_viewer(self):
270        import RemoteObjectBrowser
271        oid = self.rpcclt.remotecall("exec", "stackviewer", ("flist",), {})
272        if oid is None:
273            self.tkconsole.root.bell()
274            return
275        item = RemoteObjectBrowser.StubObjectTreeItem(self.rpcclt, oid)
276        from TreeWidget import ScrolledCanvas, TreeNode
277        top = Toplevel(self.tkconsole.root)
278        sc = ScrolledCanvas(top, bg="white", highlightthickness=0)
279        sc.frame.pack(expand=1, fill="both")
280        node = TreeNode(sc.canvas, None, item)
281        node.expand()
282        # XXX Should GC the remote tree when closing the window
283
284    gid = 0
285
286    def execsource(self, source):
287        "Like runsource() but assumes complete exec source"
288        filename = self.stuffsource(source)
289        self.execfile(filename, source)
290
291    def execfile(self, filename, source=None):
292        "Execute an existing file"
293        if source is None:
294            source = open(filename, "r").read()
295        try:
296            code = compile(source, filename, "exec")
297        except (OverflowError, SyntaxError):
298            self.tkconsole.resetoutput()
299            InteractiveInterpreter.showsyntaxerror(self, filename)
300        else:
301            self.runcode(code)
302
303    def runsource(self, source):
304        "Extend base class method: Stuff the source in the line cache first"
305        filename = self.stuffsource(source)
306        self.more = 0
307        self.save_warnings_filters = warnings.filters[:]
308        warnings.filterwarnings(action="error", category=SyntaxWarning)
309        try:
310            return InteractiveInterpreter.runsource(self, source, filename)
311        finally:
312            if self.save_warnings_filters is not None:
313                warnings.filters[:] = self.save_warnings_filters
314                self.save_warnings_filters = None
315
316    def stuffsource(self, source):
317        "Stuff source in the filename cache"
318        filename = "<pyshell#%d>" % self.gid
319        self.gid = self.gid + 1
320        lines = string.split(source, "\n")
321        linecache.cache[filename] = len(source)+1, 0, lines, filename
322        return filename
323
324    def showsyntaxerror(self, filename=None):
325        """Extend base class method: Add Colorizing
326
327        Color the offending position instead of printing it and pointing at it
328        with a caret.
329
330        """
331        text = self.tkconsole.text
332        stuff = self.unpackerror()
333        if not stuff:
334            self.tkconsole.resetoutput()
335            InteractiveInterpreter.showsyntaxerror(self, filename)
336            return
337        msg, lineno, offset, line = stuff
338        if lineno == 1:
339            pos = "iomark + %d chars" % (offset-1)
340        else:
341            pos = "iomark linestart + %d lines + %d chars" % (lineno-1,
342                                                              offset-1)
343        text.tag_add("ERROR", pos)
344        text.see(pos)
345        char = text.get(pos)
346        if char and char in string.letters + string.digits + "_":
347            text.tag_add("ERROR", pos + " wordstart", pos)
348        self.tkconsole.resetoutput()
349        self.write("SyntaxError: %s\n" % str(msg))
350
351    def unpackerror(self):
352        type, value, tb = sys.exc_info()
353        ok = type is SyntaxError
354        if ok:
355            try:
356                msg, (dummy_filename, lineno, offset, line) = value
357            except:
358                ok = 0
359        if ok:
360            return msg, lineno, offset, line
361        else:
362            return None
363
364    def showtraceback(self):
365        "Extend base class method to reset output properly"
366        self.tkconsole.resetoutput()
367        self.checklinecache()
368        InteractiveInterpreter.showtraceback(self)
369        if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
370            self.tkconsole.open_stack_viewer()
371
372    def checklinecache(self):
373        c = linecache.cache
374        for key in c.keys():
375            if key[:1] + key[-1:] != "<>":
376                del c[key]
377
378    debugger = None
379
380    def setdebugger(self, debugger):
381        self.debugger = debugger
382
383    def getdebugger(self):
384        return self.debugger
385
386    def runcommand(self, code):
387        "Run the code without invoking the debugger"
388        # The code better not raise an exception!
389        if self.tkconsole.executing:
390            tkMessageBox.showerror(
391                "Already executing",
392                "The Python Shell window is already executing a command; "
393                "please wait until it is finished.",
394                master=self.tkconsole.text)
395            return 0
396        if self.rpcclt:
397            self.rpcclt.remotecall("exec", "runcode", (code,), {})
398        else:
399            exec code in self.locals
400        return 1
401
402    def runcode(self, code):
403        "Override base class method"
404        if self.tkconsole.executing:
405            tkMessageBox.showerror(
406                "Already executing",
407                "The Python Shell window is already executing a command; "
408                "please wait until it is finished.",
409                master=self.tkconsole.text)
410            return
411        #
412        self.checklinecache()
413        if self.save_warnings_filters is not None:
414            warnings.filters[:] = self.save_warnings_filters
415            self.save_warnings_filters = None
416        debugger = self.debugger
417        if not debugger and self.rpcclt is not None:
418            self.tkconsole.beginexecuting()
419            self.active_seq = self.rpcclt.asynccall("exec", "runcode",
420                                                    (code,), {})
421            return
422        #
423        try:
424            self.tkconsole.beginexecuting()
425            try:
426                if debugger:
427                    debugger.run(code, self.locals)
428                else:
429                    exec code in self.locals
430            except SystemExit:
431                if tkMessageBox.askyesno(
432                    "Exit?",
433                    "Do you want to exit altogether?",
434                    default="yes",
435                    master=self.tkconsole.text):
436                    raise
437                else:
438                    self.showtraceback()
439            except:
440                self.showtraceback()
441        #
442        finally:
443            self.tkconsole.endexecuting()
444
445    def write(self, s):
446        "Override base class method"
447        self.tkconsole.console.write(s)
448
449class PyShell(OutputWindow):
450
451    shell_title = "Python Shell"
452
453    # Override classes
454    ColorDelegator = ModifiedColorDelegator
455    UndoDelegator = ModifiedUndoDelegator
456
457    # Override menus: Run and Format not desired in shell; add Debug
458    menu_specs = [
459        ("file", "_File"),
460        ("edit", "_Edit"),
461        ("debug", "_Debug"),
462        ("settings", "_Settings"),
463        ("windows", "_Windows"),
464        ("help", "_Help"),
465    ]
466
467    # New classes
468    from IdleHistory import History
469
470    def __init__(self, flist=None):
471        self.interp = ModifiedInterpreter(self)
472        if flist is None:
473            root = Tk()
474            fixwordbreaks(root)
475            root.withdraw()
476            flist = PyShellFileList(root)
477
478        OutputWindow.__init__(self, flist, None, None)
479
480        import __builtin__
481        __builtin__.quit = __builtin__.exit = "To exit, type Ctrl-D."
482
483        self.auto = self.extensions["AutoIndent"] # Required extension
484        self.auto.config(usetabs=1, indentwidth=8, context_use_ps1=1)
485
486        text = self.text
487        text.configure(wrap="char")
488        text.bind("<<newline-and-indent>>", self.enter_callback)
489        text.bind("<<plain-newline-and-indent>>", self.linefeed_callback)
490        text.bind("<<interrupt-execution>>", self.cancel_callback)
491        text.bind("<<beginning-of-line>>", self.home_callback)
492        text.bind("<<end-of-file>>", self.eof_callback)
493        text.bind("<<open-stack-viewer>>", self.open_stack_viewer)
494        text.bind("<<toggle-debugger>>", self.toggle_debugger)
495        text.bind("<<open-python-shell>>", self.flist.open_shell)
496        text.bind("<<toggle-jit-stack-viewer>>", self.toggle_jit_stack_viewer)
497
498        self.save_stdout = sys.stdout
499        self.save_stderr = sys.stderr
500        self.save_stdin = sys.stdin
501        self.stdout = PseudoFile(self, "stdout")
502        self.stderr = PseudoFile(self, "stderr")
503        self.console = PseudoFile(self, "console")
504        if not use_subprocess:
505            sys.stdout = self.stdout
506            sys.stderr = self.stderr
507            sys.stdin = self
508
509        self.history = self.History(self.text)
510
511        if use_subprocess:
512            self.interp.spawn_subprocess()
513
514    reading = 0
515    executing = 0
516    canceled = 0
517    endoffile = 0
518
519    def toggle_debugger(self, event=None):
520        if self.executing:
521            tkMessageBox.showerror("Don't debug now",
522                "You can only toggle the debugger when idle",
523                master=self.text)
524            self.set_debugger_indicator()
525            return "break"
526        else:
527            db = self.interp.getdebugger()
528            if db:
529                self.close_debugger()
530            else:
531                self.open_debugger()
532
533    def set_debugger_indicator(self):
534        db = self.interp.getdebugger()
535        self.setvar("<<toggle-debugger>>", not not db)
536
537    def toggle_jit_stack_viewer( self, event=None):
538        pass # All we need is the variable
539
540    def close_debugger(self):
541        db = self.interp.getdebugger()
542        if db:
543            self.interp.setdebugger(None)
544            db.close()
545            if self.interp.rpcclt:
546                RemoteDebugger.close_remote_debugger(self.interp.rpcclt)
547            self.resetoutput()
548            self.console.write("[DEBUG OFF]\n")
549            sys.ps1 = ">>> "
550            self.showprompt()
551        self.set_debugger_indicator()
552
553    def open_debugger(self):
554        # XXX KBK 13Jun02 An RPC client always exists now? Open remote
555        # debugger and return...dike the rest of this fcn and combine
556        # with open_remote_debugger?
557        if self.interp.rpcclt:
558            return self.open_remote_debugger()
559        import Debugger
560        self.interp.setdebugger(Debugger.Debugger(self))
561        sys.ps1 = "[DEBUG ON]\n>>> "
562        self.showprompt()
563        self.set_debugger_indicator()
564
565    def open_remote_debugger(self):
566        gui = RemoteDebugger.start_remote_debugger(self.interp.rpcclt, self)
567        self.interp.setdebugger(gui)
568        sys.ps1 = "[DEBUG ON]\n>>> "
569        self.showprompt()
570        self.set_debugger_indicator()
571
572    def beginexecuting(self):
573        "Helper for ModifiedInterpreter"
574        self.resetoutput()
575        self.executing = 1
576        ##self._cancel_check = self.cancel_check
577        ##sys.settrace(self._cancel_check)
578
579    def endexecuting(self):
580        "Helper for ModifiedInterpreter"
581        ##sys.settrace(None)
582        ##self._cancel_check = None
583        self.executing = 0
584        self.canceled = 0
585        self.showprompt()
586
587    def close(self):
588        "Extend EditorWindow.close()"
589        if self.executing:
590            # XXX Need to ask a question here
591            if not tkMessageBox.askokcancel(
592                "Kill?",
593                "The program is still running; do you want to kill it?",
594                default="ok",
595                master=self.text):
596                return "cancel"
597            self.canceled = 1
598            if self.reading:
599                self.top.quit()
600            return "cancel"
601        return EditorWindow.close(self)
602
603    def _close(self):
604        "Extend EditorWindow._close(), shut down debugger and execution server"
605        self.close_debugger()
606        self.interp.kill_subprocess()
607        # Restore std streams
608        sys.stdout = self.save_stdout
609        sys.stderr = self.save_stderr
610        sys.stdin = self.save_stdin
611        # Break cycles
612        self.interp = None
613        self.console = None
614        self.auto = None
615        self.flist.pyshell = None
616        self.history = None
617        EditorWindow._close(self)
618
619    def ispythonsource(self, filename):
620        "Override EditorWindow method: never remove the colorizer"
621        return 1
622
623    def short_title(self):
624        return self.shell_title
625
626    COPYRIGHT = \
627              'Type "copyright", "credits" or "license" for more information.'
628
629    def begin(self):
630        self.resetoutput()
631        self.write("Python %s on %s\n%s\nGRPC IDLE Fork %s\n" %
632                   (sys.version, sys.platform, self.COPYRIGHT,
633                    idlever.IDLE_VERSION))
634        try:
635            sys.ps1
636        except AttributeError:
637            sys.ps1 = ">>> "
638        self.showprompt()
639        import Tkinter
640        Tkinter._default_root = None
641
642    def interact(self):
643        self.begin()
644        self.top.mainloop()
645
646    def readline(self):
647        save = self.reading
648        try:
649            self.reading = 1
650            self.top.mainloop()
651        finally:
652            self.reading = save
653        line = self.text.get("iomark", "end-1c")
654        self.resetoutput()
655        if self.canceled:
656            self.canceled = 0
657            raise KeyboardInterrupt
658        if self.endoffile:
659            self.endoffile = 0
660            return ""
661        return line
662
663    def isatty(self):
664        return 1
665
666    def cancel_callback(self, event):
667        try:
668            if self.text.compare("sel.first", "!=", "sel.last"):
669                return # Active selection -- always use default binding
670        except:
671            pass
672        if not (self.executing or self.reading):
673            self.resetoutput()
674            self.write("KeyboardInterrupt\n")
675            self.showprompt()
676            return "break"
677        self.endoffile = 0
678        if self.reading:
679            self.canceled = 1
680            self.top.quit()
681        elif (self.executing and self.interp.rpcclt and
682              self.interp.rpcpid and hasattr(os, "kill")):
683            try:
684                from signal import SIGINT
685            except ImportError:
686                SIGINT = 2
687            os.kill(self.interp.rpcpid, SIGINT)
688        else:
689            self.canceled = 1
690        return "break"
691
692    def eof_callback(self, event):
693        if self.executing and not self.reading:
694            return # Let the default binding (delete next char) take over
695        if not (self.text.compare("iomark", "==", "insert") and
696                self.text.compare("insert", "==", "end-1c")):
697            return # Let the default binding (delete next char) take over
698        if not self.executing:
699            self.resetoutput()
700            self.close()
701        else:
702            self.canceled = 0
703            self.endoffile = 1
704            self.top.quit()
705        return "break"
706
707    def home_callback(self, event):
708        if event.state != 0 and event.keysym == "Home":
709            return # <Modifier-Home>; fall back to class binding
710        if self.text.compare("iomark", "<=", "insert") and \
711           self.text.compare("insert linestart", "<=", "iomark"):
712            self.text.mark_set("insert", "iomark")
713            self.text.tag_remove("sel", "1.0", "end")
714            self.text.see("insert")
715            return "break"
716
717    def linefeed_callback(self, event):
718        # Insert a linefeed without entering anything (still autoindented)
719        if self.reading:
720            self.text.insert("insert", "\n")
721            self.text.see("insert")
722        else:
723            self.auto.auto_indent(event)
724        return "break"
725
726    def enter_callback(self, event):
727        if self.executing and not self.reading:
728            return # Let the default binding (insert '\n') take over
729        # If some text is selected, recall the selection
730        # (but only if this before the I/O mark)
731        try:
732            sel = self.text.get("sel.first", "sel.last")
733            if sel:
734                if self.text.compare("sel.last", "<=", "iomark"):
735                    self.recall(sel)
736                    return "break"
737        except:
738            pass
739        # If we're strictly before the line containing iomark, recall
740        # the current line, less a leading prompt, less leading or
741        # trailing whitespace
742        if self.text.compare("insert", "<", "iomark linestart"):
743            # Check if there's a relevant stdin range -- if so, use it
744            prev = self.text.tag_prevrange("stdin", "insert")
745            if prev and self.text.compare("insert", "<", prev[1]):
746                self.recall(self.text.get(prev[0], prev[1]))
747                return "break"
748            next = self.text.tag_nextrange("stdin", "insert")
749            if next and self.text.compare("insert lineend", ">=", next[0]):
750                self.recall(self.text.get(next[0], next[1]))
751                return "break"
752            # No stdin mark -- just get the current line
753            self.recall(self.text.get("insert linestart", "insert lineend"))
754            return "break"
755        # If we're in the current input and there's only whitespace
756        # beyond the cursor, erase that whitespace first
757        s = self.text.get("insert", "end-1c")
758        if s and not string.strip(s):
759            self.text.delete("insert", "end-1c")
760        # If we're in the current input before its last line,
761        # insert a newline right at the insert point
762        if self.text.compare("insert", "<", "end-1c linestart"):
763            self.auto.auto_indent(event)
764            return "break"
765        # We're in the last line; append a newline and submit it
766        self.text.mark_set("insert", "end-1c")
767        if self.reading:
768            self.text.insert("insert", "\n")
769            self.text.see("insert")
770        else:
771            self.auto.auto_indent(event)
772        self.text.tag_add("stdin", "iomark", "end-1c")
773        self.text.update_idletasks()
774        if self.reading:
775            self.top.quit() # Break out of recursive mainloop() in raw_input()
776        else:
777            self.runit()
778        return "break"
779
780    def recall(self, s):
781        if self.history:
782            self.history.recall(s)
783
784    def runit(self):
785        line = self.text.get("iomark", "end-1c")
786        # Strip off last newline and surrounding whitespace.
787        # (To allow you to hit return twice to end a statement.)
788        i = len(line)
789        while i > 0 and line[i-1] in " \t":
790            i = i-1
791        if i > 0 and line[i-1] == "\n":
792            i = i-1
793        while i > 0 and line[i-1] in " \t":
794            i = i-1
795        line = line[:i]
796        more = self.interp.runsource(line)
797
798    def cancel_check(self, frame, what, args,
799                     dooneevent=tkinter.dooneevent,
800                     dontwait=tkinter.DONT_WAIT):
801        # Hack -- use the debugger hooks to be able to handle events
802        # and interrupt execution at any time.
803        # This slows execution down quite a bit, so you may want to
804        # disable this (by not calling settrace() in runcode() above)
805        # for full-bore (uninterruptable) speed.
806        # XXX This should become a user option.
807        if self.canceled:
808            return
809        dooneevent(dontwait)
810        if self.canceled:
811            self.canceled = 0
812            raise KeyboardInterrupt
813        return self._cancel_check
814
815    def open_stack_viewer(self, event=None):
816        if self.interp.rpcclt:
817            return self.interp.remote_stack_viewer()
818        try:
819            sys.last_traceback
820        except:
821            tkMessageBox.showerror("No stack trace",
822                "There is no stack trace yet.\n"
823                "(sys.last_traceback is not defined)",
824                master=self.text)
825            return
826        from StackViewer import StackBrowser
827        sv = StackBrowser(self.root, self.flist)
828
829    def showprompt(self):
830        self.resetoutput()
831        try:
832            s = str(sys.ps1)
833        except:
834            s = ""
835        self.console.write(s)
836        self.text.mark_set("insert", "end-1c")
837        self.set_line_and_column()
838        self.io.reset_undo()
839
840    def resetoutput(self):
841        source = self.text.get("iomark", "end-1c")
842        if self.history:
843            self.history.history_store(source)
844        if self.text.get("end-2c") != "\n":
845            self.text.insert("end-1c", "\n")
846        self.text.mark_set("iomark", "end-1c")
847        self.set_line_and_column()
848        sys.stdout.softspace = 0
849
850    def write(self, s, tags=()):
851        self.text.mark_gravity("iomark", "right")
852        OutputWindow.write(self, s, tags, "iomark")
853        self.text.mark_gravity("iomark", "left")
854        if self.canceled:
855            self.canceled = 0
856            raise KeyboardInterrupt
857
858class PseudoFile:
859
860    def __init__(self, shell, tags):
861        self.shell = shell
862        self.tags = tags
863        self.softspace = 0
864
865    def write(self, s):
866        self.shell.write(s, self.tags)
867
868    def writelines(self, l):
869        map(self.write, l)
870
871    def flush(self):
872        pass
873
874    def isatty(self):
875        return 1
876
877
878usage_msg = """\
879usage: idle.py [-c command] [-d] [-i] [-r script] [-s] [-t title] [arg] ...
880
881idle file(s)    (without options) edit the file(s)
882
883-c cmd     run the command in a shell
884-d         enable the debugger
885-e         edit mode; arguments are files to be edited
886-i         open an interactive shell
887-i file(s) open a shell and also an editor window for each file
888-r
889-s         run $IDLESTARTUP or $PYTHONSTARTUP before anything else
890-t title   set title of shell window
891
892Remaining arguments are applied to the command (-c) or script (-r).
893"""
894
895def main():
896    cmd = None
897    edit = 0
898    debug = 0
899    script = None
900    startup = 0
901
902    try:
903        opts, args = getopt.getopt(sys.argv[1:], "c:deir:st:")
904    except getopt.error, msg:
905        sys.stderr.write("Error: %s\n" % str(msg))
906        sys.stderr.write(usage_msg)
907        sys.exit(2)
908
909    for o, a in opts:
910        if o == '-c':
911            cmd = a
912        if o == '-d':
913            debug = 1
914        if o == '-e':
915            edit = 1
916        if o == '-r':
917            script = a
918        if o == '-s':
919            startup = 1
920        if o == '-t':
921            PyShell.shell_title = a
922
923    if args and args[0] != "-": edit = 1
924
925    for i in range(len(sys.path)):
926        sys.path[i] = os.path.abspath(sys.path[i])
927
928    pathx = []
929    if edit:
930        for filename in args:
931            pathx.append(os.path.dirname(filename))
932    elif args and args[0] != "-":
933        pathx.append(os.path.dirname(args[0]))
934    else:
935        pathx.append(os.curdir)
936    for dir in pathx:
937        dir = os.path.abspath(dir)
938        if not dir in sys.path:
939            sys.path.insert(0, dir)
940
941    global flist, root
942    root = Tk(className="Idle")
943    fixwordbreaks(root)
944    root.withdraw()
945    flist = PyShellFileList(root)
946
947    if edit:
948        for filename in args:
949            flist.open(filename)
950        if not args:
951            flist.new()
952    else:
953        if cmd:
954            sys.argv = ["-c"] + args
955        else:
956            sys.argv = args or [""]
957
958    shell = PyShell(flist)
959    interp = shell.interp
960    flist.pyshell = shell
961
962    if startup:
963        filename = os.environ.get("IDLESTARTUP") or \
964                   os.environ.get("PYTHONSTARTUP")
965        if filename and os.path.isfile(filename):
966            interp.execfile(filename)
967
968    if debug:
969        shell.open_debugger()
970    if cmd:
971        interp.execsource(cmd)
972    elif script:
973        if os.path.isfile(script):
974            interp.execfile(script)
975        else:
976            print "No script file: ", script
977    shell.begin()
978    root.mainloop()
979    root.destroy()
980
981def display_port_binding_error():
982    print """\
983IDLE cannot run.
984
985IDLE needs to use a specific TCP/IP port (8833) in order to execute and
986debug programs. IDLE is unable to bind to this port, and so cannot
987start. Here are some possible causes of this problem:
988
989  1. TCP/IP networking is not installed or not working on this computer
990  2. Another program is running that uses this port
991  3. Personal firewall software is preventing IDLE from using this port
992
993IDLE makes and accepts connections only with this computer, and does not
994communicate over the internet in any way. Its use of port 8833 should not
995be a security risk on a single-user machine.
996"""
997
998if __name__ == "__main__":
999    main()
1000