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