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