1#!/usr/local/bin/python3.7
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
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        # 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.interp.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 tkMessageBox.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        tkMessageBox.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        tkMessageBox.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        tkMessageBox.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 = "Python " + python_version() + " Shell"
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            tkMessageBox.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 beginexecuting(self):
993        "Helper for ModifiedInterpreter"
994        self.resetoutput()
995        self.executing = True
996
997    def endexecuting(self):
998        "Helper for ModifiedInterpreter"
999        self.executing = False
1000        self.canceled = False
1001        self.showprompt()
1002
1003    def close(self):
1004        "Extend EditorWindow.close()"
1005        if self.executing:
1006            response = tkMessageBox.askokcancel(
1007                "Kill?",
1008                "Your program is still running!\n Do you want to kill it?",
1009                default="ok",
1010                parent=self.text)
1011            if response is False:
1012                return "cancel"
1013        self.stop_readline()
1014        self.canceled = True
1015        self.closing = True
1016        return EditorWindow.close(self)
1017
1018    def _close(self):
1019        "Extend EditorWindow._close(), shut down debugger and execution server"
1020        self.close_debugger()
1021        if use_subprocess:
1022            self.interp.kill_subprocess()
1023        # Restore std streams
1024        sys.stdout = self.save_stdout
1025        sys.stderr = self.save_stderr
1026        sys.stdin = self.save_stdin
1027        # Break cycles
1028        self.interp = None
1029        self.console = None
1030        self.flist.pyshell = None
1031        self.history = None
1032        EditorWindow._close(self)
1033
1034    def ispythonsource(self, filename):
1035        "Override EditorWindow method: never remove the colorizer"
1036        return True
1037
1038    def short_title(self):
1039        return self.shell_title
1040
1041    COPYRIGHT = \
1042          'Type "help", "copyright", "credits" or "license()" for more information.'
1043
1044    def begin(self):
1045        self.text.mark_set("iomark", "insert")
1046        self.resetoutput()
1047        if use_subprocess:
1048            nosub = ''
1049            client = self.interp.start_subprocess()
1050            if not client:
1051                self.close()
1052                return False
1053        else:
1054            nosub = ("==== No Subprocess ====\n\n" +
1055                    "WARNING: Running IDLE without a Subprocess is deprecated\n" +
1056                    "and will be removed in a later version. See Help/IDLE Help\n" +
1057                    "for details.\n\n")
1058            sys.displayhook = rpc.displayhook
1059
1060        self.write("Python %s on %s\n%s\n%s" %
1061                   (sys.version, sys.platform, self.COPYRIGHT, nosub))
1062        self.text.focus_force()
1063        self.showprompt()
1064        import tkinter
1065        tkinter._default_root = None # 03Jan04 KBK What's this?
1066        return True
1067
1068    def stop_readline(self):
1069        if not self.reading:  # no nested mainloop to exit.
1070            return
1071        self._stop_readline_flag = True
1072        self.top.quit()
1073
1074    def readline(self):
1075        save = self.reading
1076        try:
1077            self.reading = True
1078            self.top.mainloop()  # nested mainloop()
1079        finally:
1080            self.reading = save
1081        if self._stop_readline_flag:
1082            self._stop_readline_flag = False
1083            return ""
1084        line = self.text.get("iomark", "end-1c")
1085        if len(line) == 0:  # may be EOF if we quit our mainloop with Ctrl-C
1086            line = "\n"
1087        self.resetoutput()
1088        if self.canceled:
1089            self.canceled = False
1090            if not use_subprocess:
1091                raise KeyboardInterrupt
1092        if self.endoffile:
1093            self.endoffile = False
1094            line = ""
1095        return line
1096
1097    def isatty(self):
1098        return True
1099
1100    def cancel_callback(self, event=None):
1101        try:
1102            if self.text.compare("sel.first", "!=", "sel.last"):
1103                return # Active selection -- always use default binding
1104        except:
1105            pass
1106        if not (self.executing or self.reading):
1107            self.resetoutput()
1108            self.interp.write("KeyboardInterrupt\n")
1109            self.showprompt()
1110            return "break"
1111        self.endoffile = False
1112        self.canceled = True
1113        if (self.executing and self.interp.rpcclt):
1114            if self.interp.getdebugger():
1115                self.interp.restart_subprocess()
1116            else:
1117                self.interp.interrupt_subprocess()
1118        if self.reading:
1119            self.top.quit()  # exit the nested mainloop() in readline()
1120        return "break"
1121
1122    def eof_callback(self, event):
1123        if self.executing and not self.reading:
1124            return # Let the default binding (delete next char) take over
1125        if not (self.text.compare("iomark", "==", "insert") and
1126                self.text.compare("insert", "==", "end-1c")):
1127            return # Let the default binding (delete next char) take over
1128        if not self.executing:
1129            self.resetoutput()
1130            self.close()
1131        else:
1132            self.canceled = False
1133            self.endoffile = True
1134            self.top.quit()
1135        return "break"
1136
1137    def linefeed_callback(self, event):
1138        # Insert a linefeed without entering anything (still autoindented)
1139        if self.reading:
1140            self.text.insert("insert", "\n")
1141            self.text.see("insert")
1142        else:
1143            self.newline_and_indent_event(event)
1144        return "break"
1145
1146    def enter_callback(self, event):
1147        if self.executing and not self.reading:
1148            return # Let the default binding (insert '\n') take over
1149        # If some text is selected, recall the selection
1150        # (but only if this before the I/O mark)
1151        try:
1152            sel = self.text.get("sel.first", "sel.last")
1153            if sel:
1154                if self.text.compare("sel.last", "<=", "iomark"):
1155                    self.recall(sel, event)
1156                    return "break"
1157        except:
1158            pass
1159        # If we're strictly before the line containing iomark, recall
1160        # the current line, less a leading prompt, less leading or
1161        # trailing whitespace
1162        if self.text.compare("insert", "<", "iomark linestart"):
1163            # Check if there's a relevant stdin range -- if so, use it
1164            prev = self.text.tag_prevrange("stdin", "insert")
1165            if prev and self.text.compare("insert", "<", prev[1]):
1166                self.recall(self.text.get(prev[0], prev[1]), event)
1167                return "break"
1168            next = self.text.tag_nextrange("stdin", "insert")
1169            if next and self.text.compare("insert lineend", ">=", next[0]):
1170                self.recall(self.text.get(next[0], next[1]), event)
1171                return "break"
1172            # No stdin mark -- just get the current line, less any prompt
1173            indices = self.text.tag_nextrange("console", "insert linestart")
1174            if indices and \
1175               self.text.compare(indices[0], "<=", "insert linestart"):
1176                self.recall(self.text.get(indices[1], "insert lineend"), event)
1177            else:
1178                self.recall(self.text.get("insert linestart", "insert lineend"), event)
1179            return "break"
1180        # If we're between the beginning of the line and the iomark, i.e.
1181        # in the prompt area, move to the end of the prompt
1182        if self.text.compare("insert", "<", "iomark"):
1183            self.text.mark_set("insert", "iomark")
1184        # If we're in the current input and there's only whitespace
1185        # beyond the cursor, erase that whitespace first
1186        s = self.text.get("insert", "end-1c")
1187        if s and not s.strip():
1188            self.text.delete("insert", "end-1c")
1189        # If we're in the current input before its last line,
1190        # insert a newline right at the insert point
1191        if self.text.compare("insert", "<", "end-1c linestart"):
1192            self.newline_and_indent_event(event)
1193            return "break"
1194        # We're in the last line; append a newline and submit it
1195        self.text.mark_set("insert", "end-1c")
1196        if self.reading:
1197            self.text.insert("insert", "\n")
1198            self.text.see("insert")
1199        else:
1200            self.newline_and_indent_event(event)
1201        self.text.tag_add("stdin", "iomark", "end-1c")
1202        self.text.update_idletasks()
1203        if self.reading:
1204            self.top.quit() # Break out of recursive mainloop()
1205        else:
1206            self.runit()
1207        return "break"
1208
1209    def recall(self, s, event):
1210        # remove leading and trailing empty or whitespace lines
1211        s = re.sub(r'^\s*\n', '' , s)
1212        s = re.sub(r'\n\s*$', '', s)
1213        lines = s.split('\n')
1214        self.text.undo_block_start()
1215        try:
1216            self.text.tag_remove("sel", "1.0", "end")
1217            self.text.mark_set("insert", "end-1c")
1218            prefix = self.text.get("insert linestart", "insert")
1219            if prefix.rstrip().endswith(':'):
1220                self.newline_and_indent_event(event)
1221                prefix = self.text.get("insert linestart", "insert")
1222            self.text.insert("insert", lines[0].strip())
1223            if len(lines) > 1:
1224                orig_base_indent = re.search(r'^([ \t]*)', lines[0]).group(0)
1225                new_base_indent  = re.search(r'^([ \t]*)', prefix).group(0)
1226                for line in lines[1:]:
1227                    if line.startswith(orig_base_indent):
1228                        # replace orig base indentation with new indentation
1229                        line = new_base_indent + line[len(orig_base_indent):]
1230                    self.text.insert('insert', '\n'+line.rstrip())
1231        finally:
1232            self.text.see("insert")
1233            self.text.undo_block_stop()
1234
1235    def runit(self):
1236        line = self.text.get("iomark", "end-1c")
1237        # Strip off last newline and surrounding whitespace.
1238        # (To allow you to hit return twice to end a statement.)
1239        i = len(line)
1240        while i > 0 and line[i-1] in " \t":
1241            i = i-1
1242        if i > 0 and line[i-1] == "\n":
1243            i = i-1
1244        while i > 0 and line[i-1] in " \t":
1245            i = i-1
1246        line = line[:i]
1247        self.interp.runsource(line)
1248
1249    def open_stack_viewer(self, event=None):
1250        if self.interp.rpcclt:
1251            return self.interp.remote_stack_viewer()
1252        try:
1253            sys.last_traceback
1254        except:
1255            tkMessageBox.showerror("No stack trace",
1256                "There is no stack trace yet.\n"
1257                "(sys.last_traceback is not defined)",
1258                parent=self.text)
1259            return
1260        from idlelib.stackviewer import StackBrowser
1261        StackBrowser(self.root, self.flist)
1262
1263    def view_restart_mark(self, event=None):
1264        self.text.see("iomark")
1265        self.text.see("restart")
1266
1267    def restart_shell(self, event=None):
1268        "Callback for Run/Restart Shell Cntl-F6"
1269        self.interp.restart_subprocess(with_cwd=True)
1270
1271    def showprompt(self):
1272        self.resetoutput()
1273        self.console.write(self.prompt)
1274        self.text.mark_set("insert", "end-1c")
1275        self.set_line_and_column()
1276        self.io.reset_undo()
1277
1278    def show_warning(self, msg):
1279        width = self.interp.tkconsole.width
1280        wrapper = TextWrapper(width=width, tabsize=8, expand_tabs=True)
1281        wrapped_msg = '\n'.join(wrapper.wrap(msg))
1282        if not wrapped_msg.endswith('\n'):
1283            wrapped_msg += '\n'
1284        self.per.bottom.insert("iomark linestart", wrapped_msg, "stderr")
1285
1286    def resetoutput(self):
1287        source = self.text.get("iomark", "end-1c")
1288        if self.history:
1289            self.history.store(source)
1290        if self.text.get("end-2c") != "\n":
1291            self.text.insert("end-1c", "\n")
1292        self.text.mark_set("iomark", "end-1c")
1293        self.set_line_and_column()
1294        self.ctip.remove_calltip_window()
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 = False
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        if TkVersion >= 8.6:
1489            ext = '.png'
1490            sizes = (16, 32, 48, 256)
1491        else:
1492            ext = '.gif'
1493            sizes = (16, 32, 48)
1494        iconfiles = [os.path.join(icondir, 'idle_%d%s' % (size, ext))
1495                     for size in sizes]
1496        icons = [PhotoImage(master=root, file=iconfile)
1497                 for iconfile in iconfiles]
1498        root.wm_iconphoto(True, *icons)
1499
1500    # start editor and/or shell windows:
1501    fixwordbreaks(root)
1502    fix_x11_paste(root)
1503    flist = PyShellFileList(root)
1504    macosx.setupApp(root, flist)
1505
1506    if enable_edit:
1507        if not (cmd or script):
1508            for filename in args[:]:
1509                if flist.open(filename) is None:
1510                    # filename is a directory actually, disconsider it
1511                    args.remove(filename)
1512            if not args:
1513                flist.new()
1514
1515    if enable_shell:
1516        shell = flist.open_shell()
1517        if not shell:
1518            return # couldn't open shell
1519        if macosx.isAquaTk() and flist.dict:
1520            # On OSX: when the user has double-clicked on a file that causes
1521            # IDLE to be launched the shell window will open just in front of
1522            # the file she wants to see. Lower the interpreter window when
1523            # there are open files.
1524            shell.top.lower()
1525    else:
1526        shell = flist.pyshell
1527
1528    # Handle remaining options. If any of these are set, enable_shell
1529    # was set also, so shell must be true to reach here.
1530    if debug:
1531        shell.open_debugger()
1532    if startup:
1533        filename = os.environ.get("IDLESTARTUP") or \
1534                   os.environ.get("PYTHONSTARTUP")
1535        if filename and os.path.isfile(filename):
1536            shell.interp.execfile(filename)
1537    if cmd or script:
1538        shell.interp.runcommand("""if 1:
1539            import sys as _sys
1540            _sys.argv = %r
1541            del _sys
1542            \n""" % (sys.argv,))
1543        if cmd:
1544            shell.interp.execsource(cmd)
1545        elif script:
1546            shell.interp.prepend_syspath(script)
1547            shell.interp.execfile(script)
1548    elif shell:
1549        # If there is a shell window and no cmd or script in progress,
1550        # check for problematic issues and print warning message(s) in
1551        # the IDLE shell window; this is less intrusive than always
1552        # opening a separate window.
1553
1554        # Warn if using a problematic OS X Tk version.
1555        tkversionwarning = macosx.tkVersionWarning(root)
1556        if tkversionwarning:
1557            shell.show_warning(tkversionwarning)
1558
1559        # Warn if the "Prefer tabs when opening documents" system
1560        # preference is set to "Always".
1561        prefer_tabs_preference_warning = macosx.preferTabsPreferenceWarning()
1562        if prefer_tabs_preference_warning:
1563            shell.show_warning(prefer_tabs_preference_warning)
1564
1565    while flist.inversedict:  # keep IDLE running while files are open.
1566        root.mainloop()
1567    root.destroy()
1568    capture_warnings(False)
1569
1570if __name__ == "__main__":
1571    main()
1572
1573capture_warnings(False)  # Make sure turned off; see issue 18081
1574