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