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