PyShell.py revision 6f805942290b8d83f0e229de98c8d0d7a2a7c3e8
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
299
300class MyRPCClient(rpc.RPCClient):
301
302    def handle_EOF(self):
303        "Override the base class - just re-raise EOFError"
304        raise EOFError
305
306
307class ModifiedInterpreter(InteractiveInterpreter):
308
309    def __init__(self, tkconsole):
310        self.tkconsole = tkconsole
311        locals = sys.modules['__main__'].__dict__
312        InteractiveInterpreter.__init__(self, locals=locals)
313        self.save_warnings_filters = None
314        self.restarting = False
315
316    port = 8833
317    rpcclt = None
318    rpcpid = None
319
320    def spawn_subprocess(self):
321        args = self.build_subprocess_arglist()
322        self.rpcpid = os.spawnv(os.P_NOWAIT, args[0], args)
323
324    def build_subprocess_arglist(self):
325        w = ['-W' + s for s in sys.warnoptions]
326        # Maybe IDLE is installed and is being accessed via sys.path,
327        # or maybe it's not installed and the idle.py script is being
328        # run from the IDLE source directory.
329        if __name__ == 'idlelib.PyShell':
330            command = "__import__('idlelib.run').run.main()"
331        else:
332            command = "__import__('run').main()"
333        return [sys.executable] + w + ["-c", command, str(self.port)]
334
335    def start_subprocess(self):
336        addr = ("localhost", self.port)
337        # Idle starts listening for connection on localhost
338        for i in range(3):
339            time.sleep(i)
340            try:
341                self.rpcclt = MyRPCClient(addr)
342                break
343            except socket.error, err:
344                print>>sys.__stderr__,"IDLE socket error: " + err[1]\
345                                                    + ", retrying..."
346        else:
347            display_port_binding_error()
348            sys.exit()
349        self.spawn_subprocess()
350        # Accept the connection from the Python execution server
351        self.rpcclt.accept()
352        self.rpcclt.register("stdin", self.tkconsole)
353        self.rpcclt.register("stdout", self.tkconsole.stdout)
354        self.rpcclt.register("stderr", self.tkconsole.stderr)
355        self.rpcclt.register("flist", self.tkconsole.flist)
356        self.rpcclt.register("linecache", linecache)
357        self.transfer_path()
358        self.poll_subprocess()
359
360    def restart_subprocess(self):
361        if self.restarting:
362            return
363        self.restarting = True
364        # close only the subprocess debugger
365        debug = self.getdebugger()
366        if debug:
367            try:
368                # Only close subprocess debugger, don't unregister gui_adap!
369                RemoteDebugger.close_subprocess_debugger(self.rpcclt)
370            except:
371                pass
372        # Kill subprocess, spawn a new one, accept connection.
373        self.rpcclt.close()
374        self.unix_terminate()
375        console = self.tkconsole
376        was_executing = console.executing
377        console.executing = False
378        self.spawn_subprocess()
379        self.rpcclt.accept()
380        self.transfer_path()
381        # annotate restart in shell window and mark it
382        console.text.delete("iomark", "end-1c")
383        if was_executing:
384            console.write('\n')
385            console.showprompt()
386        halfbar = ((int(console.width) - 16) // 2) * '='
387        console.write(halfbar + ' RESTART ' + halfbar)
388        console.text.mark_set("restart", "end-1c")
389        console.text.mark_gravity("restart", "left")
390        console.showprompt()
391        # restart subprocess debugger
392        if debug:
393            # Restarted debugger connects to current instance of debug GUI
394            gui = RemoteDebugger.restart_subprocess_debugger(self.rpcclt)
395            # reload remote debugger breakpoints for all PyShellEditWindows
396            debug.load_breakpoints()
397        self.restarting = False
398
399    def __request_interrupt(self):
400        self.rpcclt.remotecall("exec", "interrupt_the_server", (), {})
401
402    def interrupt_subprocess(self):
403        threading.Thread(target=self.__request_interrupt).start()
404
405    def kill_subprocess(self):
406        self.rpcclt.close()
407        self.unix_terminate()
408        self.tkconsole.executing = False
409        self.rpcclt = None
410
411    def unix_terminate(self):
412        "UNIX: make sure subprocess is terminated and collect status"
413        if hasattr(os, 'kill'):
414            try:
415                os.kill(self.rpcpid, SIGTERM)
416            except OSError:
417                # process already terminated:
418                return
419            else:
420                try:
421                    os.waitpid(self.rpcpid, 0)
422                except OSError:
423                    return
424
425    def transfer_path(self):
426        self.runcommand("""if 1:
427        import sys as _sys
428        _sys.path = %s
429        del _sys
430        _msg = 'Use File/Exit or your end-of-file key to quit IDLE'
431        __builtins__.quit = __builtins__.exit = _msg
432        del _msg
433        \n""" % `sys.path`)
434
435    active_seq = None
436
437    def poll_subprocess(self):
438        clt = self.rpcclt
439        if clt is None:
440            return
441        try:
442            response = clt.pollresponse(self.active_seq, wait=0.05)
443        except (EOFError, IOError, KeyboardInterrupt):
444            # lost connection or subprocess terminated itself, restart
445            # [the KBI is from rpc.SocketIO.handle_EOF()]
446            if self.tkconsole.closing:
447                return
448            response = None
449            self.restart_subprocess()
450        if response:
451            self.tkconsole.resetoutput()
452            self.active_seq = None
453            how, what = response
454            console = self.tkconsole.console
455            if how == "OK":
456                if what is not None:
457                    print >>console, `what`
458            elif how == "EXCEPTION":
459                if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
460                    self.remote_stack_viewer()
461            elif how == "ERROR":
462                errmsg = "PyShell.ModifiedInterpreter: Subprocess ERROR:\n"
463                print >>sys.__stderr__, errmsg, what
464                print >>console, errmsg, what
465            # we received a response to the currently active seq number:
466            self.tkconsole.endexecuting()
467        # Reschedule myself
468        if not self.tkconsole.closing:
469            self.tkconsole.text.after(self.tkconsole.pollinterval,
470                                      self.poll_subprocess)
471
472    debugger = None
473
474    def setdebugger(self, debugger):
475        self.debugger = debugger
476
477    def getdebugger(self):
478        return self.debugger
479
480    def remote_stack_viewer(self):
481        import RemoteObjectBrowser
482        oid = self.rpcclt.remotequeue("exec", "stackviewer", ("flist",), {})
483        if oid is None:
484            self.tkconsole.root.bell()
485            return
486        item = RemoteObjectBrowser.StubObjectTreeItem(self.rpcclt, oid)
487        from TreeWidget import ScrolledCanvas, TreeNode
488        top = Toplevel(self.tkconsole.root)
489        sc = ScrolledCanvas(top, bg="white", highlightthickness=0)
490        sc.frame.pack(expand=1, fill="both")
491        node = TreeNode(sc.canvas, None, item)
492        node.expand()
493        # XXX Should GC the remote tree when closing the window
494
495    gid = 0
496
497    def execsource(self, source):
498        "Like runsource() but assumes complete exec source"
499        filename = self.stuffsource(source)
500        self.execfile(filename, source)
501
502    def execfile(self, filename, source=None):
503        "Execute an existing file"
504        if source is None:
505            source = open(filename, "r").read()
506        try:
507            code = compile(source, filename, "exec")
508        except (OverflowError, SyntaxError):
509            self.tkconsole.resetoutput()
510            tkerr = self.tkconsole.stderr
511            print>>tkerr, '*** Error in script or command!\n'
512            print>>tkerr, 'Traceback (most recent call last):'
513            InteractiveInterpreter.showsyntaxerror(self, filename)
514            self.tkconsole.showprompt()
515        else:
516            self.runcode(code)
517
518    def runsource(self, source):
519        "Extend base class method: Stuff the source in the line cache first"
520        filename = self.stuffsource(source)
521        self.more = 0
522        self.save_warnings_filters = warnings.filters[:]
523        warnings.filterwarnings(action="error", category=SyntaxWarning)
524        if isinstance(source, types.UnicodeType):
525            import IOBinding
526            try:
527                source = source.encode(IOBinding.encoding)
528            except UnicodeError:
529                self.tkconsole.resetoutput()
530                self.write("Unsupported characters in input")
531                return
532        try:
533            return InteractiveInterpreter.runsource(self, source, filename)
534        finally:
535            if self.save_warnings_filters is not None:
536                warnings.filters[:] = self.save_warnings_filters
537                self.save_warnings_filters = None
538
539    def stuffsource(self, source):
540        "Stuff source in the filename cache"
541        filename = "<pyshell#%d>" % self.gid
542        self.gid = self.gid + 1
543        lines = source.split("\n")
544        linecache.cache[filename] = len(source)+1, 0, lines, filename
545        return filename
546
547    def prepend_syspath(self, filename):
548        "Prepend sys.path with file's directory if not already included"
549        self.runcommand("""if 1:
550            _filename = %s
551            import sys as _sys
552            from os.path import dirname as _dirname
553            _dir = _dirname(_filename)
554            if not _dir in _sys.path:
555                _sys.path.insert(0, _dir)
556            del _filename, _sys, _dirname, _dir
557            \n""" % `filename`)
558
559    def showsyntaxerror(self, filename=None):
560        """Extend base class method: Add Colorizing
561
562        Color the offending position instead of printing it and pointing at it
563        with a caret.
564
565        """
566        text = self.tkconsole.text
567        stuff = self.unpackerror()
568        if stuff:
569            msg, lineno, offset, line = stuff
570            if lineno == 1:
571                pos = "iomark + %d chars" % (offset-1)
572            else:
573                pos = "iomark linestart + %d lines + %d chars" % \
574                      (lineno-1, offset-1)
575            text.tag_add("ERROR", pos)
576            text.see(pos)
577            char = text.get(pos)
578            if char and char in IDENTCHARS:
579                text.tag_add("ERROR", pos + " wordstart", pos)
580            self.tkconsole.resetoutput()
581            self.write("SyntaxError: %s\n" % str(msg))
582        else:
583            self.tkconsole.resetoutput()
584            InteractiveInterpreter.showsyntaxerror(self, filename)
585        self.tkconsole.showprompt()
586
587    def unpackerror(self):
588        type, value, tb = sys.exc_info()
589        ok = type is SyntaxError
590        if ok:
591            try:
592                msg, (dummy_filename, lineno, offset, line) = value
593            except:
594                ok = 0
595        if ok:
596            return msg, lineno, offset, line
597        else:
598            return None
599
600    def showtraceback(self):
601        "Extend base class method to reset output properly"
602        self.tkconsole.resetoutput()
603        self.checklinecache()
604        InteractiveInterpreter.showtraceback(self)
605        if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
606            self.tkconsole.open_stack_viewer()
607
608    def checklinecache(self):
609        c = linecache.cache
610        for key in c.keys():
611            if key[:1] + key[-1:] != "<>":
612                del c[key]
613
614    def display_executing_dialog(self):
615        tkMessageBox.showerror(
616            "Already executing",
617            "The Python Shell window is already executing a command; "
618            "please wait until it is finished.",
619            master=self.tkconsole.text)
620
621    def runcommand(self, code):
622        "Run the code without invoking the debugger"
623        # The code better not raise an exception!
624        if self.tkconsole.executing:
625            self.display_executing_dialog()
626            return 0
627        if self.rpcclt:
628            self.rpcclt.remotequeue("exec", "runcode", (code,), {})
629        else:
630            exec code in self.locals
631        return 1
632
633    def runcode(self, code):
634        "Override base class method"
635        if self.tkconsole.executing:
636            self.interp.restart_subprocess()
637        self.checklinecache()
638        if self.save_warnings_filters is not None:
639            warnings.filters[:] = self.save_warnings_filters
640            self.save_warnings_filters = None
641        debugger = self.debugger
642        try:
643            self.tkconsole.beginexecuting()
644            try:
645                if not debugger and self.rpcclt is not None:
646                    self.active_seq = self.rpcclt.asyncqueue("exec", "runcode",
647                                                            (code,), {})
648                elif debugger:
649                    debugger.run(code, self.locals)
650                else:
651                    exec code in self.locals
652            except SystemExit:
653                if tkMessageBox.askyesno(
654                    "Exit?",
655                    "Do you want to exit altogether?",
656                    default="yes",
657                    master=self.tkconsole.text):
658                    raise
659                else:
660                    self.showtraceback()
661            except:
662                self.showtraceback()
663        finally:
664            if not use_subprocess:
665                self.tkconsole.endexecuting()
666
667    def write(self, s):
668        "Override base class method"
669        self.tkconsole.stderr.write(s)
670
671class PyShell(OutputWindow):
672
673    shell_title = "Python Shell"
674
675    # Override classes
676    ColorDelegator = ModifiedColorDelegator
677    UndoDelegator = ModifiedUndoDelegator
678
679    # Override menus
680    menu_specs = [
681        ("file", "_File"),
682        ("edit", "_Edit"),
683        ("debug", "_Debug"),
684        ("options", "_Options"),
685        ("windows", "_Windows"),
686        ("help", "_Help"),
687    ]
688
689    # New classes
690    from IdleHistory import History
691
692    def __init__(self, flist=None):
693        if use_subprocess:
694            ms = self.menu_specs
695            if ms[2][0] != "shell":
696                ms.insert(2, ("shell", "_Shell"))
697        self.interp = ModifiedInterpreter(self)
698        if flist is None:
699            root = Tk()
700            fixwordbreaks(root)
701            root.withdraw()
702            flist = PyShellFileList(root)
703        #
704        OutputWindow.__init__(self, flist, None, None)
705        #
706        import __builtin__
707        __builtin__.quit = __builtin__.exit = "To exit, type Ctrl-D."
708        #
709        self.config(usetabs=1, indentwidth=8, context_use_ps1=1)
710        #
711        text = self.text
712        text.configure(wrap="char")
713        text.bind("<<newline-and-indent>>", self.enter_callback)
714        text.bind("<<plain-newline-and-indent>>", self.linefeed_callback)
715        text.bind("<<interrupt-execution>>", self.cancel_callback)
716        text.bind("<<beginning-of-line>>", self.home_callback)
717        text.bind("<<end-of-file>>", self.eof_callback)
718        text.bind("<<open-stack-viewer>>", self.open_stack_viewer)
719        text.bind("<<toggle-debugger>>", self.toggle_debugger)
720        text.bind("<<open-python-shell>>", self.flist.open_shell)
721        text.bind("<<toggle-jit-stack-viewer>>", self.toggle_jit_stack_viewer)
722        if use_subprocess:
723            text.bind("<<view-restart>>", self.view_restart_mark)
724            text.bind("<<restart-shell>>", self.restart_shell)
725        #
726        self.save_stdout = sys.stdout
727        self.save_stderr = sys.stderr
728        self.save_stdin = sys.stdin
729        self.stdout = PseudoFile(self, "stdout")
730        self.stderr = PseudoFile(self, "stderr")
731        self.console = PseudoFile(self, "console")
732        if not use_subprocess:
733            sys.stdout = self.stdout
734            sys.stderr = self.stderr
735            sys.stdin = self
736        #
737        self.history = self.History(self.text)
738        #
739        self.pollinterval = 50  # millisec
740        if use_subprocess:
741            self.interp.start_subprocess()
742
743    reading = False
744    executing = False
745    canceled = False
746    endoffile = False
747    closing = False
748
749    def toggle_debugger(self, event=None):
750        if self.executing:
751            tkMessageBox.showerror("Don't debug now",
752                "You can only toggle the debugger when idle",
753                master=self.text)
754            self.set_debugger_indicator()
755            return "break"
756        else:
757            db = self.interp.getdebugger()
758            if db:
759                self.close_debugger()
760            else:
761                self.open_debugger()
762
763    def set_debugger_indicator(self):
764        db = self.interp.getdebugger()
765        self.setvar("<<toggle-debugger>>", not not db)
766
767    def toggle_jit_stack_viewer(self, event=None):
768        pass # All we need is the variable
769
770    def close_debugger(self):
771        db = self.interp.getdebugger()
772        if db:
773            self.interp.setdebugger(None)
774            db.close()
775            if self.interp.rpcclt:
776                RemoteDebugger.close_remote_debugger(self.interp.rpcclt)
777            self.resetoutput()
778            self.console.write("[DEBUG OFF]\n")
779            sys.ps1 = ">>> "
780            self.showprompt()
781        self.set_debugger_indicator()
782
783    def open_debugger(self):
784        if self.interp.rpcclt:
785            dbg_gui = RemoteDebugger.start_remote_debugger(self.interp.rpcclt,
786                                                           self)
787        else:
788            dbg_gui = Debugger.Debugger(self)
789        self.interp.setdebugger(dbg_gui)
790        dbg_gui.load_breakpoints()
791        sys.ps1 = "[DEBUG ON]\n>>> "
792        self.showprompt()
793        self.set_debugger_indicator()
794
795    def beginexecuting(self):
796        "Helper for ModifiedInterpreter"
797        self.resetoutput()
798        self.executing = 1
799
800    def endexecuting(self):
801        "Helper for ModifiedInterpreter"
802        self.executing = 0
803        self.canceled = 0
804        self.showprompt()
805
806    def close(self):
807        "Extend EditorWindow.close()"
808        if self.executing:
809            response = tkMessageBox.askokcancel(
810                "Kill?",
811                "The program is still running!\n Do you want to kill it?",
812                default="ok",
813                parent=self.text)
814            if response == False:
815                return "cancel"
816        self.closing = True
817        # Wait for poll_subprocess() rescheduling to stop
818        self.text.after(2 * self.pollinterval, self.close2)
819
820    def close2(self):
821        return EditorWindow.close(self)
822
823    def _close(self):
824        "Extend EditorWindow._close(), shut down debugger and execution server"
825        self.close_debugger()
826        if use_subprocess:
827            self.interp.kill_subprocess()
828        # Restore std streams
829        sys.stdout = self.save_stdout
830        sys.stderr = self.save_stderr
831        sys.stdin = self.save_stdin
832        # Break cycles
833        self.interp = None
834        self.console = None
835        self.flist.pyshell = None
836        self.history = None
837        EditorWindow._close(self)
838
839    def ispythonsource(self, filename):
840        "Override EditorWindow method: never remove the colorizer"
841        return True
842
843    def short_title(self):
844        return self.shell_title
845
846    COPYRIGHT = \
847          'Type "copyright", "credits" or "license()" for more information.'
848
849    def begin(self):
850        self.resetoutput()
851        if use_subprocess:
852            nosub = ''
853        else:
854            nosub = "==== No Subprocess ===="
855        self.write("Python %s on %s\n%s\nIDLEfork %s      %s\n" %
856                   (sys.version, sys.platform, self.COPYRIGHT,
857                    idlever.IDLE_VERSION, nosub))
858        self.showprompt()
859        import Tkinter
860        Tkinter._default_root = None
861
862    def interact(self):
863        self.begin()
864        self.top.mainloop()
865
866    def readline(self):
867        save = self.reading
868        try:
869            self.reading = 1
870            self.top.mainloop()
871        finally:
872            self.reading = save
873        line = self.text.get("iomark", "end-1c")
874        self.resetoutput()
875        if self.canceled:
876            self.canceled = 0
877            raise KeyboardInterrupt
878        if self.endoffile:
879            self.endoffile = 0
880            return ""
881        return line
882
883    def isatty(self):
884        return True
885
886    def cancel_callback(self, event=None):
887        try:
888            if self.text.compare("sel.first", "!=", "sel.last"):
889                return # Active selection -- always use default binding
890        except:
891            pass
892        if not (self.executing or self.reading):
893            self.resetoutput()
894            self.interp.write("KeyboardInterrupt\n")
895            self.showprompt()
896            return "break"
897        self.endoffile = 0
898        self.canceled = 1
899        if self.reading:
900            self.top.quit()
901        elif (self.executing and self.interp.rpcclt):
902            if self.interp.getdebugger():
903                self.interp.restart_subprocess()
904            else:
905                self.interp.interrupt_subprocess()
906        return "break"
907
908    def eof_callback(self, event):
909        if self.executing and not self.reading:
910            return # Let the default binding (delete next char) take over
911        if not (self.text.compare("iomark", "==", "insert") and
912                self.text.compare("insert", "==", "end-1c")):
913            return # Let the default binding (delete next char) take over
914        if not self.executing:
915            self.resetoutput()
916            self.close()
917        else:
918            self.canceled = 0
919            self.endoffile = 1
920            self.top.quit()
921        return "break"
922
923    def home_callback(self, event):
924        if event.state != 0 and event.keysym == "Home":
925            return # <Modifier-Home>; fall back to class binding
926        if self.text.compare("iomark", "<=", "insert") and \
927           self.text.compare("insert linestart", "<=", "iomark"):
928            self.text.mark_set("insert", "iomark")
929            self.text.tag_remove("sel", "1.0", "end")
930            self.text.see("insert")
931            return "break"
932
933    def linefeed_callback(self, event):
934        # Insert a linefeed without entering anything (still autoindented)
935        if self.reading:
936            self.text.insert("insert", "\n")
937            self.text.see("insert")
938        else:
939            self.newline_and_indent_event(event)
940        return "break"
941
942    def enter_callback(self, event):
943        if self.executing and not self.reading:
944            return # Let the default binding (insert '\n') take over
945        # If some text is selected, recall the selection
946        # (but only if this before the I/O mark)
947        try:
948            sel = self.text.get("sel.first", "sel.last")
949            if sel:
950                if self.text.compare("sel.last", "<=", "iomark"):
951                    self.recall(sel)
952                    return "break"
953        except:
954            pass
955        # If we're strictly before the line containing iomark, recall
956        # the current line, less a leading prompt, less leading or
957        # trailing whitespace
958        if self.text.compare("insert", "<", "iomark linestart"):
959            # Check if there's a relevant stdin range -- if so, use it
960            prev = self.text.tag_prevrange("stdin", "insert")
961            if prev and self.text.compare("insert", "<", prev[1]):
962                self.recall(self.text.get(prev[0], prev[1]))
963                return "break"
964            next = self.text.tag_nextrange("stdin", "insert")
965            if next and self.text.compare("insert lineend", ">=", next[0]):
966                self.recall(self.text.get(next[0], next[1]))
967                return "break"
968            # No stdin mark -- just get the current line, less any prompt
969            line = self.text.get("insert linestart", "insert lineend")
970            last_line_of_prompt = sys.ps1.split('\n')[-1]
971            if line.startswith(last_line_of_prompt):
972                line = line[len(last_line_of_prompt):]
973            self.recall(line)
974            return "break"
975        # If we're between the beginning of the line and the iomark, i.e.
976        # in the prompt area, move to the end of the prompt
977        if self.text.compare("insert", "<", "iomark"):
978            self.text.mark_set("insert", "iomark")
979        # If we're in the current input and there's only whitespace
980        # beyond the cursor, erase that whitespace first
981        s = self.text.get("insert", "end-1c")
982        if s and not s.strip():
983            self.text.delete("insert", "end-1c")
984        # If we're in the current input before its last line,
985        # insert a newline right at the insert point
986        if self.text.compare("insert", "<", "end-1c linestart"):
987            self.newline_and_indent_event(event)
988            return "break"
989        # We're in the last line; append a newline and submit it
990        self.text.mark_set("insert", "end-1c")
991        if self.reading:
992            self.text.insert("insert", "\n")
993            self.text.see("insert")
994        else:
995            self.newline_and_indent_event(event)
996        self.text.tag_add("stdin", "iomark", "end-1c")
997        self.text.update_idletasks()
998        if self.reading:
999            self.top.quit() # Break out of recursive mainloop() in raw_input()
1000        else:
1001            self.runit()
1002        return "break"
1003
1004    def recall(self, s):
1005        if self.history:
1006            self.history.recall(s)
1007
1008    def runit(self):
1009        line = self.text.get("iomark", "end-1c")
1010        # Strip off last newline and surrounding whitespace.
1011        # (To allow you to hit return twice to end a statement.)
1012        i = len(line)
1013        while i > 0 and line[i-1] in " \t":
1014            i = i-1
1015        if i > 0 and line[i-1] == "\n":
1016            i = i-1
1017        while i > 0 and line[i-1] in " \t":
1018            i = i-1
1019        line = line[:i]
1020        more = self.interp.runsource(line)
1021
1022    def open_stack_viewer(self, event=None):
1023        if self.interp.rpcclt:
1024            return self.interp.remote_stack_viewer()
1025        try:
1026            sys.last_traceback
1027        except:
1028            tkMessageBox.showerror("No stack trace",
1029                "There is no stack trace yet.\n"
1030                "(sys.last_traceback is not defined)",
1031                master=self.text)
1032            return
1033        from StackViewer import StackBrowser
1034        sv = StackBrowser(self.root, self.flist)
1035
1036    def view_restart_mark(self, event=None):
1037        self.text.see("iomark")
1038        self.text.see("restart")
1039
1040    def restart_shell(self, event=None):
1041        self.interp.restart_subprocess()
1042
1043    def showprompt(self):
1044        self.resetoutput()
1045        try:
1046            s = str(sys.ps1)
1047        except:
1048            s = ""
1049        self.console.write(s)
1050        self.text.mark_set("insert", "end-1c")
1051        self.set_line_and_column()
1052        self.io.reset_undo()
1053
1054    def resetoutput(self):
1055        source = self.text.get("iomark", "end-1c")
1056        if self.history:
1057            self.history.history_store(source)
1058        if self.text.get("end-2c") != "\n":
1059            self.text.insert("end-1c", "\n")
1060        self.text.mark_set("iomark", "end-1c")
1061        self.set_line_and_column()
1062        sys.stdout.softspace = 0
1063
1064    def write(self, s, tags=()):
1065        try:
1066            self.text.mark_gravity("iomark", "right")
1067            OutputWindow.write(self, s, tags, "iomark")
1068            self.text.mark_gravity("iomark", "left")
1069        except:
1070            pass
1071        if self.canceled:
1072            self.canceled = 0
1073            if not use_subprocess:
1074                raise KeyboardInterrupt
1075
1076class PseudoFile:
1077
1078    def __init__(self, shell, tags):
1079        self.shell = shell
1080        self.tags = tags
1081        self.softspace = 0
1082
1083    def write(self, s):
1084        self.shell.write(s, self.tags)
1085
1086    def writelines(self, l):
1087        map(self.write, l)
1088
1089    def flush(self):
1090        pass
1091
1092    def isatty(self):
1093        return True
1094
1095
1096usage_msg = """\
1097
1098USAGE: idle  [-deins] [-t title] [file]*
1099       idle  [-dns] [-t title] (-c cmd | -r file) [arg]*
1100       idle  [-dns] [-t title] - [arg]*
1101
1102  -h         print this help message and exit
1103  -n         run IDLE without a subprocess (see Help/IDLE Help for details)
1104
1105The following options will override the IDLE 'settings' configuration:
1106
1107  -e         open an edit window
1108  -i         open a shell window
1109
1110The following options imply -i and will open a shell:
1111
1112  -c cmd     run the command in a shell, or
1113  -r file    run script from file
1114
1115  -d         enable the debugger
1116  -s         run $IDLESTARTUP or $PYTHONSTARTUP before anything else
1117  -t title   set title of shell window
1118
1119A default edit window will be bypassed when -c, -r, or - are used.
1120
1121[arg]* are passed to the command (-c) or script (-r) in sys.argv[1:].
1122
1123Examples:
1124
1125idle
1126        Open an edit window or shell depending on IDLE's configuration.
1127
1128idle foo.py foobar.py
1129        Edit the files, also open a shell if configured to start with shell.
1130
1131idle -est "Baz" foo.py
1132        Run $IDLESTARTUP or $PYTHONSTARTUP, edit foo.py, and open a shell
1133        window with the title "Baz".
1134
1135idle -c "import sys; print sys.argv" "foo"
1136        Open a shell window and run the command, passing "-c" in sys.argv[0]
1137        and "foo" in sys.argv[1].
1138
1139idle -d -s -r foo.py "Hello World"
1140        Open a shell window, run a startup script, enable the debugger, and
1141        run foo.py, passing "foo.py" in sys.argv[0] and "Hello World" in
1142        sys.argv[1].
1143
1144echo "import sys; print sys.argv" | idle - "foobar"
1145        Open a shell window, run the script piped in, passing '' in sys.argv[0]
1146        and "foobar" in sys.argv[1].
1147"""
1148
1149def main():
1150    global flist, root, use_subprocess
1151
1152    use_subprocess = True
1153    enable_shell = False
1154    enable_edit = False
1155    debug = False
1156    cmd = None
1157    script = None
1158    startup = False
1159    try:
1160        sys.ps1
1161    except AttributeError:
1162        sys.ps1 = '>>> '
1163    try:
1164        opts, args = getopt.getopt(sys.argv[1:], "c:deihnr:st:")
1165    except getopt.error, msg:
1166        sys.stderr.write("Error: %s\n" % str(msg))
1167        sys.stderr.write(usage_msg)
1168        sys.exit(2)
1169    for o, a in opts:
1170        if o == '-c':
1171            cmd = a
1172            enable_shell = True
1173        if o == '-d':
1174            debug = True
1175            enable_shell = True
1176        if o == '-e':
1177            enable_edit = True
1178        if o == '-h':
1179            sys.stdout.write(usage_msg)
1180            sys.exit()
1181        if o == '-i':
1182            enable_shell = True
1183        if o == '-n':
1184            use_subprocess = False
1185        if o == '-r':
1186            script = a
1187            if os.path.isfile(script):
1188                pass
1189            else:
1190                print "No script file: ", script
1191                sys.exit()
1192            enable_shell = True
1193        if o == '-s':
1194            startup = True
1195            enable_shell = True
1196        if o == '-t':
1197            PyShell.shell_title = a
1198            enable_shell = True
1199    if args and args[0] == '-':
1200        cmd = sys.stdin.read()
1201        enable_shell = True
1202    # process sys.argv and sys.path:
1203    for i in range(len(sys.path)):
1204        sys.path[i] = os.path.abspath(sys.path[i])
1205    if args and args[0] == '-':
1206        sys.argv = [''] + args[1:]
1207    elif cmd:
1208        sys.argv = ['-c'] + args
1209    elif script:
1210        sys.argv = [script] + args
1211    elif args:
1212        enable_edit = True
1213        pathx = []
1214        for filename in args:
1215            pathx.append(os.path.dirname(filename))
1216        for dir in pathx:
1217            dir = os.path.abspath(dir)
1218            if not dir in sys.path:
1219                sys.path.insert(0, dir)
1220    else:
1221        dir = os.getcwd()
1222        if not dir in sys.path:
1223            sys.path.insert(0, dir)
1224    # check the IDLE settings configuration (but command line overrides)
1225    edit_start = idleConf.GetOption('main', 'General',
1226                                    'editor-on-startup', type='bool')
1227    enable_edit = enable_edit or edit_start
1228    enable_shell = enable_shell or not edit_start
1229    # start editor and/or shell windows:
1230    root = Tk(className="Idle")
1231    fixwordbreaks(root)
1232    root.withdraw()
1233    flist = PyShellFileList(root)
1234    if enable_edit:
1235        if not (cmd or script):
1236            for filename in args:
1237                flist.open(filename)
1238            if not args:
1239                flist.new()
1240        if enable_shell:
1241            flist.open_shell()
1242    elif enable_shell:
1243        flist.pyshell = PyShell(flist)
1244        flist.pyshell.begin()
1245    shell = flist.pyshell
1246    # handle remaining options:
1247    if debug:
1248        shell.open_debugger()
1249    if startup:
1250        filename = os.environ.get("IDLESTARTUP") or \
1251                   os.environ.get("PYTHONSTARTUP")
1252        if filename and os.path.isfile(filename):
1253            shell.interp.execfile(filename)
1254    if cmd or script:
1255        shell.interp.runcommand("""if 1:
1256            import sys as _sys
1257            _sys.argv = %s
1258            del _sys
1259            \n""" % `sys.argv`)
1260        if cmd:
1261            shell.interp.execsource(cmd)
1262        elif script:
1263            shell.interp.prepend_syspath(script)
1264            shell.interp.execfile(script)
1265    root.mainloop()
1266    root.destroy()
1267
1268
1269def display_port_binding_error():
1270    print """\
1271\nIDLE cannot run.
1272
1273IDLE needs to use a specific TCP/IP port (8833) in order to communicate with
1274its Python execution server.  IDLE is unable to bind to this port, and so
1275cannot start. Here are some possible causes of this problem:
1276
1277  1. TCP/IP networking is not installed or not working on this computer
1278  2. Another program (another IDLE?) is running that uses this port
1279  3. Personal firewall software is preventing IDLE from using this port
1280
1281Run IDLE with the -n command line switch to start without a subprocess
1282and refer to Help/IDLE Help "Running without a subprocess" for further
1283details.
1284"""
1285
1286if __name__ == "__main__":
1287    main()
1288