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