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  # Int required.
20        ctypes.OleDLL('shcore').SetProcessDpiAwareness(PROCESS_SYSTEM_DPI_AWARE)
21    except (ImportError, AttributeError, OSError):
22        pass
23
24from tkinter import messagebox
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    messagebox.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                messagebox.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        # at the moment, InteractiveInterpreter expects str
680        assert isinstance(source, str)
681        # InteractiveInterpreter.runsource() calls its runcode() method,
682        # which is overridden (see below)
683        return InteractiveInterpreter.runsource(self, source, filename)
684
685    def stuffsource(self, source):
686        "Stuff source in the filename cache"
687        filename = "<pyshell#%d>" % self.gid
688        self.gid = self.gid + 1
689        lines = source.split("\n")
690        linecache.cache[filename] = len(source)+1, 0, lines, filename
691        return filename
692
693    def prepend_syspath(self, filename):
694        "Prepend sys.path with file's directory if not already included"
695        self.runcommand("""if 1:
696            _filename = %r
697            import sys as _sys
698            from os.path import dirname as _dirname
699            _dir = _dirname(_filename)
700            if not _dir in _sys.path:
701                _sys.path.insert(0, _dir)
702            del _filename, _sys, _dirname, _dir
703            \n""" % (filename,))
704
705    def showsyntaxerror(self, filename=None):
706        """Override Interactive Interpreter method: Use Colorizing
707
708        Color the offending position instead of printing it and pointing at it
709        with a caret.
710
711        """
712        tkconsole = self.tkconsole
713        text = tkconsole.text
714        text.tag_remove("ERROR", "1.0", "end")
715        type, value, tb = sys.exc_info()
716        msg = getattr(value, 'msg', '') or value or "<no detail available>"
717        lineno = getattr(value, 'lineno', '') or 1
718        offset = getattr(value, 'offset', '') or 0
719        if offset == 0:
720            lineno += 1 #mark end of offending line
721        if lineno == 1:
722            pos = "iomark + %d chars" % (offset-1)
723        else:
724            pos = "iomark linestart + %d lines + %d chars" % \
725                  (lineno-1, offset-1)
726        tkconsole.colorize_syntax_error(text, pos)
727        tkconsole.resetoutput()
728        self.write("SyntaxError: %s\n" % msg)
729        tkconsole.showprompt()
730
731    def showtraceback(self):
732        "Extend base class method to reset output properly"
733        self.tkconsole.resetoutput()
734        self.checklinecache()
735        InteractiveInterpreter.showtraceback(self)
736        if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
737            self.tkconsole.open_stack_viewer()
738
739    def checklinecache(self):
740        c = linecache.cache
741        for key in list(c.keys()):
742            if key[:1] + key[-1:] != "<>":
743                del c[key]
744
745    def runcommand(self, code):
746        "Run the code without invoking the debugger"
747        # The code better not raise an exception!
748        if self.tkconsole.executing:
749            self.display_executing_dialog()
750            return 0
751        if self.rpcclt:
752            self.rpcclt.remotequeue("exec", "runcode", (code,), {})
753        else:
754            exec(code, self.locals)
755        return 1
756
757    def runcode(self, code):
758        "Override base class method"
759        if self.tkconsole.executing:
760            self.restart_subprocess()
761        self.checklinecache()
762        debugger = self.debugger
763        try:
764            self.tkconsole.beginexecuting()
765            if not debugger and self.rpcclt is not None:
766                self.active_seq = self.rpcclt.asyncqueue("exec", "runcode",
767                                                        (code,), {})
768            elif debugger:
769                debugger.run(code, self.locals)
770            else:
771                exec(code, self.locals)
772        except SystemExit:
773            if not self.tkconsole.closing:
774                if messagebox.askyesno(
775                    "Exit?",
776                    "Do you want to exit altogether?",
777                    default="yes",
778                    parent=self.tkconsole.text):
779                    raise
780                else:
781                    self.showtraceback()
782            else:
783                raise
784        except:
785            if use_subprocess:
786                print("IDLE internal error in runcode()",
787                      file=self.tkconsole.stderr)
788                self.showtraceback()
789                self.tkconsole.endexecuting()
790            else:
791                if self.tkconsole.canceled:
792                    self.tkconsole.canceled = False
793                    print("KeyboardInterrupt", file=self.tkconsole.stderr)
794                else:
795                    self.showtraceback()
796        finally:
797            if not use_subprocess:
798                try:
799                    self.tkconsole.endexecuting()
800                except AttributeError:  # shell may have closed
801                    pass
802
803    def write(self, s):
804        "Override base class method"
805        return self.tkconsole.stderr.write(s)
806
807    def display_port_binding_error(self):
808        messagebox.showerror(
809            "Port Binding Error",
810            "IDLE can't bind to a TCP/IP port, which is necessary to "
811            "communicate with its Python execution server.  This might be "
812            "because no networking is installed on this computer.  "
813            "Run IDLE with the -n command line switch to start without a "
814            "subprocess and refer to Help/IDLE Help 'Running without a "
815            "subprocess' for further details.",
816            parent=self.tkconsole.text)
817
818    def display_no_subprocess_error(self):
819        messagebox.showerror(
820            "Subprocess Connection Error",
821            "IDLE's subprocess didn't make connection.\n"
822            "See the 'Startup failure' section of the IDLE doc, online at\n"
823            "https://docs.python.org/3/library/idle.html#startup-failure",
824            parent=self.tkconsole.text)
825
826    def display_executing_dialog(self):
827        messagebox.showerror(
828            "Already executing",
829            "The Python Shell window is already executing a command; "
830            "please wait until it is finished.",
831            parent=self.tkconsole.text)
832
833
834class PyShell(OutputWindow):
835
836    shell_title = "IDLE Shell " + python_version()
837
838    # Override classes
839    ColorDelegator = ModifiedColorDelegator
840    UndoDelegator = ModifiedUndoDelegator
841
842    # Override menus
843    menu_specs = [
844        ("file", "_File"),
845        ("edit", "_Edit"),
846        ("debug", "_Debug"),
847        ("options", "_Options"),
848        ("window", "_Window"),
849        ("help", "_Help"),
850    ]
851
852    # Extend right-click context menu
853    rmenu_specs = OutputWindow.rmenu_specs + [
854        ("Squeeze", "<<squeeze-current-text>>"),
855    ]
856
857    allow_line_numbers = False
858
859    # New classes
860    from idlelib.history import History
861
862    def __init__(self, flist=None):
863        if use_subprocess:
864            ms = self.menu_specs
865            if ms[2][0] != "shell":
866                ms.insert(2, ("shell", "She_ll"))
867        self.interp = ModifiedInterpreter(self)
868        if flist is None:
869            root = Tk()
870            fixwordbreaks(root)
871            root.withdraw()
872            flist = PyShellFileList(root)
873
874        OutputWindow.__init__(self, flist, None, None)
875
876        self.usetabs = True
877        # indentwidth must be 8 when using tabs.  See note in EditorWindow:
878        self.indentwidth = 8
879
880        self.sys_ps1 = sys.ps1 if hasattr(sys, 'ps1') else '>>> '
881        self.prompt_last_line = self.sys_ps1.split('\n')[-1]
882        self.prompt = self.sys_ps1  # Changes when debug active
883
884        text = self.text
885        text.configure(wrap="char")
886        text.bind("<<newline-and-indent>>", self.enter_callback)
887        text.bind("<<plain-newline-and-indent>>", self.linefeed_callback)
888        text.bind("<<interrupt-execution>>", self.cancel_callback)
889        text.bind("<<end-of-file>>", self.eof_callback)
890        text.bind("<<open-stack-viewer>>", self.open_stack_viewer)
891        text.bind("<<toggle-debugger>>", self.toggle_debugger)
892        text.bind("<<toggle-jit-stack-viewer>>", self.toggle_jit_stack_viewer)
893        if use_subprocess:
894            text.bind("<<view-restart>>", self.view_restart_mark)
895            text.bind("<<restart-shell>>", self.restart_shell)
896        squeezer = self.Squeezer(self)
897        text.bind("<<squeeze-current-text>>",
898                  squeezer.squeeze_current_text_event)
899
900        self.save_stdout = sys.stdout
901        self.save_stderr = sys.stderr
902        self.save_stdin = sys.stdin
903        from idlelib import iomenu
904        self.stdin = StdInputFile(self, "stdin",
905                                  iomenu.encoding, iomenu.errors)
906        self.stdout = StdOutputFile(self, "stdout",
907                                    iomenu.encoding, iomenu.errors)
908        self.stderr = StdOutputFile(self, "stderr",
909                                    iomenu.encoding, "backslashreplace")
910        self.console = StdOutputFile(self, "console",
911                                     iomenu.encoding, iomenu.errors)
912        if not use_subprocess:
913            sys.stdout = self.stdout
914            sys.stderr = self.stderr
915            sys.stdin = self.stdin
916        try:
917            # page help() text to shell.
918            import pydoc # import must be done here to capture i/o rebinding.
919            # XXX KBK 27Dec07 use text viewer someday, but must work w/o subproc
920            pydoc.pager = pydoc.plainpager
921        except:
922            sys.stderr = sys.__stderr__
923            raise
924        #
925        self.history = self.History(self.text)
926        #
927        self.pollinterval = 50  # millisec
928
929    def get_standard_extension_names(self):
930        return idleConf.GetExtensions(shell_only=True)
931
932    reading = False
933    executing = False
934    canceled = False
935    endoffile = False
936    closing = False
937    _stop_readline_flag = False
938
939    def set_warning_stream(self, stream):
940        global warning_stream
941        warning_stream = stream
942
943    def get_warning_stream(self):
944        return warning_stream
945
946    def toggle_debugger(self, event=None):
947        if self.executing:
948            messagebox.showerror("Don't debug now",
949                "You can only toggle the debugger when idle",
950                parent=self.text)
951            self.set_debugger_indicator()
952            return "break"
953        else:
954            db = self.interp.getdebugger()
955            if db:
956                self.close_debugger()
957            else:
958                self.open_debugger()
959
960    def set_debugger_indicator(self):
961        db = self.interp.getdebugger()
962        self.setvar("<<toggle-debugger>>", not not db)
963
964    def toggle_jit_stack_viewer(self, event=None):
965        pass # All we need is the variable
966
967    def close_debugger(self):
968        db = self.interp.getdebugger()
969        if db:
970            self.interp.setdebugger(None)
971            db.close()
972            if self.interp.rpcclt:
973                debugger_r.close_remote_debugger(self.interp.rpcclt)
974            self.resetoutput()
975            self.console.write("[DEBUG OFF]\n")
976            self.prompt = self.sys_ps1
977            self.showprompt()
978        self.set_debugger_indicator()
979
980    def open_debugger(self):
981        if self.interp.rpcclt:
982            dbg_gui = debugger_r.start_remote_debugger(self.interp.rpcclt,
983                                                           self)
984        else:
985            dbg_gui = debugger.Debugger(self)
986        self.interp.setdebugger(dbg_gui)
987        dbg_gui.load_breakpoints()
988        self.prompt = "[DEBUG ON]\n" + self.sys_ps1
989        self.showprompt()
990        self.set_debugger_indicator()
991
992    def debug_menu_postcommand(self):
993        state = 'disabled' if self.executing else 'normal'
994        self.update_menu_state('debug', '*tack*iewer', state)
995
996    def beginexecuting(self):
997        "Helper for ModifiedInterpreter"
998        self.resetoutput()
999        self.executing = True
1000
1001    def endexecuting(self):
1002        "Helper for ModifiedInterpreter"
1003        self.executing = False
1004        self.canceled = False
1005        self.showprompt()
1006
1007    def close(self):
1008        "Extend EditorWindow.close()"
1009        if self.executing:
1010            response = messagebox.askokcancel(
1011                "Kill?",
1012                "Your program is still running!\n Do you want to kill it?",
1013                default="ok",
1014                parent=self.text)
1015            if response is False:
1016                return "cancel"
1017        self.stop_readline()
1018        self.canceled = True
1019        self.closing = True
1020        return EditorWindow.close(self)
1021
1022    def _close(self):
1023        "Extend EditorWindow._close(), shut down debugger and execution server"
1024        self.close_debugger()
1025        if use_subprocess:
1026            self.interp.kill_subprocess()
1027        # Restore std streams
1028        sys.stdout = self.save_stdout
1029        sys.stderr = self.save_stderr
1030        sys.stdin = self.save_stdin
1031        # Break cycles
1032        self.interp = None
1033        self.console = None
1034        self.flist.pyshell = None
1035        self.history = None
1036        EditorWindow._close(self)
1037
1038    def ispythonsource(self, filename):
1039        "Override EditorWindow method: never remove the colorizer"
1040        return True
1041
1042    def short_title(self):
1043        return self.shell_title
1044
1045    COPYRIGHT = \
1046          'Type "help", "copyright", "credits" or "license()" for more information.'
1047
1048    def begin(self):
1049        self.text.mark_set("iomark", "insert")
1050        self.resetoutput()
1051        if use_subprocess:
1052            nosub = ''
1053            client = self.interp.start_subprocess()
1054            if not client:
1055                self.close()
1056                return False
1057        else:
1058            nosub = ("==== No Subprocess ====\n\n" +
1059                    "WARNING: Running IDLE without a Subprocess is deprecated\n" +
1060                    "and will be removed in a later version. See Help/IDLE Help\n" +
1061                    "for details.\n\n")
1062            sys.displayhook = rpc.displayhook
1063
1064        self.write("Python %s on %s\n%s\n%s" %
1065                   (sys.version, sys.platform, self.COPYRIGHT, nosub))
1066        self.text.focus_force()
1067        self.showprompt()
1068        # User code should use separate default Tk root window
1069        import tkinter
1070        tkinter._support_default_root = True
1071        tkinter._default_root = None
1072        return True
1073
1074    def stop_readline(self):
1075        if not self.reading:  # no nested mainloop to exit.
1076            return
1077        self._stop_readline_flag = True
1078        self.top.quit()
1079
1080    def readline(self):
1081        save = self.reading
1082        try:
1083            self.reading = True
1084            self.top.mainloop()  # nested mainloop()
1085        finally:
1086            self.reading = save
1087        if self._stop_readline_flag:
1088            self._stop_readline_flag = False
1089            return ""
1090        line = self.text.get("iomark", "end-1c")
1091        if len(line) == 0:  # may be EOF if we quit our mainloop with Ctrl-C
1092            line = "\n"
1093        self.resetoutput()
1094        if self.canceled:
1095            self.canceled = False
1096            if not use_subprocess:
1097                raise KeyboardInterrupt
1098        if self.endoffile:
1099            self.endoffile = False
1100            line = ""
1101        return line
1102
1103    def isatty(self):
1104        return True
1105
1106    def cancel_callback(self, event=None):
1107        try:
1108            if self.text.compare("sel.first", "!=", "sel.last"):
1109                return # Active selection -- always use default binding
1110        except:
1111            pass
1112        if not (self.executing or self.reading):
1113            self.resetoutput()
1114            self.interp.write("KeyboardInterrupt\n")
1115            self.showprompt()
1116            return "break"
1117        self.endoffile = False
1118        self.canceled = True
1119        if (self.executing and self.interp.rpcclt):
1120            if self.interp.getdebugger():
1121                self.interp.restart_subprocess()
1122            else:
1123                self.interp.interrupt_subprocess()
1124        if self.reading:
1125            self.top.quit()  # exit the nested mainloop() in readline()
1126        return "break"
1127
1128    def eof_callback(self, event):
1129        if self.executing and not self.reading:
1130            return # Let the default binding (delete next char) take over
1131        if not (self.text.compare("iomark", "==", "insert") and
1132                self.text.compare("insert", "==", "end-1c")):
1133            return # Let the default binding (delete next char) take over
1134        if not self.executing:
1135            self.resetoutput()
1136            self.close()
1137        else:
1138            self.canceled = False
1139            self.endoffile = True
1140            self.top.quit()
1141        return "break"
1142
1143    def linefeed_callback(self, event):
1144        # Insert a linefeed without entering anything (still autoindented)
1145        if self.reading:
1146            self.text.insert("insert", "\n")
1147            self.text.see("insert")
1148        else:
1149            self.newline_and_indent_event(event)
1150        return "break"
1151
1152    def enter_callback(self, event):
1153        if self.executing and not self.reading:
1154            return # Let the default binding (insert '\n') take over
1155        # If some text is selected, recall the selection
1156        # (but only if this before the I/O mark)
1157        try:
1158            sel = self.text.get("sel.first", "sel.last")
1159            if sel:
1160                if self.text.compare("sel.last", "<=", "iomark"):
1161                    self.recall(sel, event)
1162                    return "break"
1163        except:
1164            pass
1165        # If we're strictly before the line containing iomark, recall
1166        # the current line, less a leading prompt, less leading or
1167        # trailing whitespace
1168        if self.text.compare("insert", "<", "iomark linestart"):
1169            # Check if there's a relevant stdin range -- if so, use it
1170            prev = self.text.tag_prevrange("stdin", "insert")
1171            if prev and self.text.compare("insert", "<", prev[1]):
1172                self.recall(self.text.get(prev[0], prev[1]), event)
1173                return "break"
1174            next = self.text.tag_nextrange("stdin", "insert")
1175            if next and self.text.compare("insert lineend", ">=", next[0]):
1176                self.recall(self.text.get(next[0], next[1]), event)
1177                return "break"
1178            # No stdin mark -- just get the current line, less any prompt
1179            indices = self.text.tag_nextrange("console", "insert linestart")
1180            if indices and \
1181               self.text.compare(indices[0], "<=", "insert linestart"):
1182                self.recall(self.text.get(indices[1], "insert lineend"), event)
1183            else:
1184                self.recall(self.text.get("insert linestart", "insert lineend"), event)
1185            return "break"
1186        # If we're between the beginning of the line and the iomark, i.e.
1187        # in the prompt area, move to the end of the prompt
1188        if self.text.compare("insert", "<", "iomark"):
1189            self.text.mark_set("insert", "iomark")
1190        # If we're in the current input and there's only whitespace
1191        # beyond the cursor, erase that whitespace first
1192        s = self.text.get("insert", "end-1c")
1193        if s and not s.strip():
1194            self.text.delete("insert", "end-1c")
1195        # If we're in the current input before its last line,
1196        # insert a newline right at the insert point
1197        if self.text.compare("insert", "<", "end-1c linestart"):
1198            self.newline_and_indent_event(event)
1199            return "break"
1200        # We're in the last line; append a newline and submit it
1201        self.text.mark_set("insert", "end-1c")
1202        if self.reading:
1203            self.text.insert("insert", "\n")
1204            self.text.see("insert")
1205        else:
1206            self.newline_and_indent_event(event)
1207        self.text.tag_add("stdin", "iomark", "end-1c")
1208        self.text.update_idletasks()
1209        if self.reading:
1210            self.top.quit() # Break out of recursive mainloop()
1211        else:
1212            self.runit()
1213        return "break"
1214
1215    def recall(self, s, event):
1216        # remove leading and trailing empty or whitespace lines
1217        s = re.sub(r'^\s*\n', '' , s)
1218        s = re.sub(r'\n\s*$', '', s)
1219        lines = s.split('\n')
1220        self.text.undo_block_start()
1221        try:
1222            self.text.tag_remove("sel", "1.0", "end")
1223            self.text.mark_set("insert", "end-1c")
1224            prefix = self.text.get("insert linestart", "insert")
1225            if prefix.rstrip().endswith(':'):
1226                self.newline_and_indent_event(event)
1227                prefix = self.text.get("insert linestart", "insert")
1228            self.text.insert("insert", lines[0].strip())
1229            if len(lines) > 1:
1230                orig_base_indent = re.search(r'^([ \t]*)', lines[0]).group(0)
1231                new_base_indent  = re.search(r'^([ \t]*)', prefix).group(0)
1232                for line in lines[1:]:
1233                    if line.startswith(orig_base_indent):
1234                        # replace orig base indentation with new indentation
1235                        line = new_base_indent + line[len(orig_base_indent):]
1236                    self.text.insert('insert', '\n'+line.rstrip())
1237        finally:
1238            self.text.see("insert")
1239            self.text.undo_block_stop()
1240
1241    def runit(self):
1242        line = self.text.get("iomark", "end-1c")
1243        # Strip off last newline and surrounding whitespace.
1244        # (To allow you to hit return twice to end a statement.)
1245        i = len(line)
1246        while i > 0 and line[i-1] in " \t":
1247            i = i-1
1248        if i > 0 and line[i-1] == "\n":
1249            i = i-1
1250        while i > 0 and line[i-1] in " \t":
1251            i = i-1
1252        line = line[:i]
1253        self.interp.runsource(line)
1254
1255    def open_stack_viewer(self, event=None):
1256        if self.interp.rpcclt:
1257            return self.interp.remote_stack_viewer()
1258        try:
1259            sys.last_traceback
1260        except:
1261            messagebox.showerror("No stack trace",
1262                "There is no stack trace yet.\n"
1263                "(sys.last_traceback is not defined)",
1264                parent=self.text)
1265            return
1266        from idlelib.stackviewer import StackBrowser
1267        StackBrowser(self.root, self.flist)
1268
1269    def view_restart_mark(self, event=None):
1270        self.text.see("iomark")
1271        self.text.see("restart")
1272
1273    def restart_shell(self, event=None):
1274        "Callback for Run/Restart Shell Cntl-F6"
1275        self.interp.restart_subprocess(with_cwd=True)
1276
1277    def showprompt(self):
1278        self.resetoutput()
1279        self.console.write(self.prompt)
1280        self.text.mark_set("insert", "end-1c")
1281        self.set_line_and_column()
1282        self.io.reset_undo()
1283
1284    def show_warning(self, msg):
1285        width = self.interp.tkconsole.width
1286        wrapper = TextWrapper(width=width, tabsize=8, expand_tabs=True)
1287        wrapped_msg = '\n'.join(wrapper.wrap(msg))
1288        if not wrapped_msg.endswith('\n'):
1289            wrapped_msg += '\n'
1290        self.per.bottom.insert("iomark linestart", wrapped_msg, "stderr")
1291
1292    def resetoutput(self):
1293        source = self.text.get("iomark", "end-1c")
1294        if self.history:
1295            self.history.store(source)
1296        if self.text.get("end-2c") != "\n":
1297            self.text.insert("end-1c", "\n")
1298        self.text.mark_set("iomark", "end-1c")
1299        self.set_line_and_column()
1300        self.ctip.remove_calltip_window()
1301
1302    def write(self, s, tags=()):
1303        try:
1304            self.text.mark_gravity("iomark", "right")
1305            count = OutputWindow.write(self, s, tags, "iomark")
1306            self.text.mark_gravity("iomark", "left")
1307        except:
1308            raise ###pass  # ### 11Aug07 KBK if we are expecting exceptions
1309                           # let's find out what they are and be specific.
1310        if self.canceled:
1311            self.canceled = False
1312            if not use_subprocess:
1313                raise KeyboardInterrupt
1314        return count
1315
1316    def rmenu_check_cut(self):
1317        try:
1318            if self.text.compare('sel.first', '<', 'iomark'):
1319                return 'disabled'
1320        except TclError: # no selection, so the index 'sel.first' doesn't exist
1321            return 'disabled'
1322        return super().rmenu_check_cut()
1323
1324    def rmenu_check_paste(self):
1325        if self.text.compare('insert','<','iomark'):
1326            return 'disabled'
1327        return super().rmenu_check_paste()
1328
1329
1330def fix_x11_paste(root):
1331    "Make paste replace selection on x11.  See issue #5124."
1332    if root._windowingsystem == 'x11':
1333        for cls in 'Text', 'Entry', 'Spinbox':
1334            root.bind_class(
1335                cls,
1336                '<<Paste>>',
1337                'catch {%W delete sel.first sel.last}\n' +
1338                        root.bind_class(cls, '<<Paste>>'))
1339
1340
1341usage_msg = """\
1342
1343USAGE: idle  [-deins] [-t title] [file]*
1344       idle  [-dns] [-t title] (-c cmd | -r file) [arg]*
1345       idle  [-dns] [-t title] - [arg]*
1346
1347  -h         print this help message and exit
1348  -n         run IDLE without a subprocess (DEPRECATED,
1349             see Help/IDLE Help for details)
1350
1351The following options will override the IDLE 'settings' configuration:
1352
1353  -e         open an edit window
1354  -i         open a shell window
1355
1356The following options imply -i and will open a shell:
1357
1358  -c cmd     run the command in a shell, or
1359  -r file    run script from file
1360
1361  -d         enable the debugger
1362  -s         run $IDLESTARTUP or $PYTHONSTARTUP before anything else
1363  -t title   set title of shell window
1364
1365A default edit window will be bypassed when -c, -r, or - are used.
1366
1367[arg]* are passed to the command (-c) or script (-r) in sys.argv[1:].
1368
1369Examples:
1370
1371idle
1372        Open an edit window or shell depending on IDLE's configuration.
1373
1374idle foo.py foobar.py
1375        Edit the files, also open a shell if configured to start with shell.
1376
1377idle -est "Baz" foo.py
1378        Run $IDLESTARTUP or $PYTHONSTARTUP, edit foo.py, and open a shell
1379        window with the title "Baz".
1380
1381idle -c "import sys; print(sys.argv)" "foo"
1382        Open a shell window and run the command, passing "-c" in sys.argv[0]
1383        and "foo" in sys.argv[1].
1384
1385idle -d -s -r foo.py "Hello World"
1386        Open a shell window, run a startup script, enable the debugger, and
1387        run foo.py, passing "foo.py" in sys.argv[0] and "Hello World" in
1388        sys.argv[1].
1389
1390echo "import sys; print(sys.argv)" | idle - "foobar"
1391        Open a shell window, run the script piped in, passing '' in sys.argv[0]
1392        and "foobar" in sys.argv[1].
1393"""
1394
1395def main():
1396    import getopt
1397    from platform import system
1398    from idlelib import testing  # bool value
1399    from idlelib import macosx
1400
1401    global flist, root, use_subprocess
1402
1403    capture_warnings(True)
1404    use_subprocess = True
1405    enable_shell = False
1406    enable_edit = False
1407    debug = False
1408    cmd = None
1409    script = None
1410    startup = False
1411    try:
1412        opts, args = getopt.getopt(sys.argv[1:], "c:deihnr:st:")
1413    except getopt.error as msg:
1414        print("Error: %s\n%s" % (msg, usage_msg), file=sys.stderr)
1415        sys.exit(2)
1416    for o, a in opts:
1417        if o == '-c':
1418            cmd = a
1419            enable_shell = True
1420        if o == '-d':
1421            debug = True
1422            enable_shell = True
1423        if o == '-e':
1424            enable_edit = True
1425        if o == '-h':
1426            sys.stdout.write(usage_msg)
1427            sys.exit()
1428        if o == '-i':
1429            enable_shell = True
1430        if o == '-n':
1431            print(" Warning: running IDLE without a subprocess is deprecated.",
1432                  file=sys.stderr)
1433            use_subprocess = False
1434        if o == '-r':
1435            script = a
1436            if os.path.isfile(script):
1437                pass
1438            else:
1439                print("No script file: ", script)
1440                sys.exit()
1441            enable_shell = True
1442        if o == '-s':
1443            startup = True
1444            enable_shell = True
1445        if o == '-t':
1446            PyShell.shell_title = a
1447            enable_shell = True
1448    if args and args[0] == '-':
1449        cmd = sys.stdin.read()
1450        enable_shell = True
1451    # process sys.argv and sys.path:
1452    for i in range(len(sys.path)):
1453        sys.path[i] = os.path.abspath(sys.path[i])
1454    if args and args[0] == '-':
1455        sys.argv = [''] + args[1:]
1456    elif cmd:
1457        sys.argv = ['-c'] + args
1458    elif script:
1459        sys.argv = [script] + args
1460    elif args:
1461        enable_edit = True
1462        pathx = []
1463        for filename in args:
1464            pathx.append(os.path.dirname(filename))
1465        for dir in pathx:
1466            dir = os.path.abspath(dir)
1467            if not dir in sys.path:
1468                sys.path.insert(0, dir)
1469    else:
1470        dir = os.getcwd()
1471        if dir not in sys.path:
1472            sys.path.insert(0, dir)
1473    # check the IDLE settings configuration (but command line overrides)
1474    edit_start = idleConf.GetOption('main', 'General',
1475                                    'editor-on-startup', type='bool')
1476    enable_edit = enable_edit or edit_start
1477    enable_shell = enable_shell or not enable_edit
1478
1479    # Setup root.  Don't break user code run in IDLE process.
1480    # Don't change environment when testing.
1481    if use_subprocess and not testing:
1482        NoDefaultRoot()
1483    root = Tk(className="Idle")
1484    root.withdraw()
1485    from idlelib.run import fix_scaling
1486    fix_scaling(root)
1487
1488    # set application icon
1489    icondir = os.path.join(os.path.dirname(__file__), 'Icons')
1490    if system() == 'Windows':
1491        iconfile = os.path.join(icondir, 'idle.ico')
1492        root.wm_iconbitmap(default=iconfile)
1493    elif not macosx.isAquaTk():
1494        if TkVersion >= 8.6:
1495            ext = '.png'
1496            sizes = (16, 32, 48, 256)
1497        else:
1498            ext = '.gif'
1499            sizes = (16, 32, 48)
1500        iconfiles = [os.path.join(icondir, 'idle_%d%s' % (size, ext))
1501                     for size in sizes]
1502        icons = [PhotoImage(master=root, file=iconfile)
1503                 for iconfile in iconfiles]
1504        root.wm_iconphoto(True, *icons)
1505
1506    # start editor and/or shell windows:
1507    fixwordbreaks(root)
1508    fix_x11_paste(root)
1509    flist = PyShellFileList(root)
1510    macosx.setupApp(root, flist)
1511
1512    if enable_edit:
1513        if not (cmd or script):
1514            for filename in args[:]:
1515                if flist.open(filename) is None:
1516                    # filename is a directory actually, disconsider it
1517                    args.remove(filename)
1518            if not args:
1519                flist.new()
1520
1521    if enable_shell:
1522        shell = flist.open_shell()
1523        if not shell:
1524            return # couldn't open shell
1525        if macosx.isAquaTk() and flist.dict:
1526            # On OSX: when the user has double-clicked on a file that causes
1527            # IDLE to be launched the shell window will open just in front of
1528            # the file she wants to see. Lower the interpreter window when
1529            # there are open files.
1530            shell.top.lower()
1531    else:
1532        shell = flist.pyshell
1533
1534    # Handle remaining options. If any of these are set, enable_shell
1535    # was set also, so shell must be true to reach here.
1536    if debug:
1537        shell.open_debugger()
1538    if startup:
1539        filename = os.environ.get("IDLESTARTUP") or \
1540                   os.environ.get("PYTHONSTARTUP")
1541        if filename and os.path.isfile(filename):
1542            shell.interp.execfile(filename)
1543    if cmd or script:
1544        shell.interp.runcommand("""if 1:
1545            import sys as _sys
1546            _sys.argv = %r
1547            del _sys
1548            \n""" % (sys.argv,))
1549        if cmd:
1550            shell.interp.execsource(cmd)
1551        elif script:
1552            shell.interp.prepend_syspath(script)
1553            shell.interp.execfile(script)
1554    elif shell:
1555        # If there is a shell window and no cmd or script in progress,
1556        # check for problematic issues and print warning message(s) in
1557        # the IDLE shell window; this is less intrusive than always
1558        # opening a separate window.
1559
1560        # Warn if using a problematic OS X Tk version.
1561        tkversionwarning = macosx.tkVersionWarning(root)
1562        if tkversionwarning:
1563            shell.show_warning(tkversionwarning)
1564
1565        # Warn if the "Prefer tabs when opening documents" system
1566        # preference is set to "Always".
1567        prefer_tabs_preference_warning = macosx.preferTabsPreferenceWarning()
1568        if prefer_tabs_preference_warning:
1569            shell.show_warning(prefer_tabs_preference_warning)
1570
1571    while flist.inversedict:  # keep IDLE running while files are open.
1572        root.mainloop()
1573    root.destroy()
1574    capture_warnings(False)
1575
1576if __name__ == "__main__":
1577    main()
1578
1579capture_warnings(False)  # Make sure turned off; see issue 18081
1580