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