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