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