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