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