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