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