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