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