17db96d56Sopenharmony_ci""" idlelib.run
27db96d56Sopenharmony_ci
37db96d56Sopenharmony_ciSimplified, pyshell.ModifiedInterpreter spawns a subprocess with
47db96d56Sopenharmony_cif'''{sys.executable} -c "__import__('idlelib.run').run.main()"'''
57db96d56Sopenharmony_ci'.run' is needed because __import__ returns idlelib, not idlelib.run.
67db96d56Sopenharmony_ci"""
77db96d56Sopenharmony_ciimport contextlib
87db96d56Sopenharmony_ciimport functools
97db96d56Sopenharmony_ciimport io
107db96d56Sopenharmony_ciimport linecache
117db96d56Sopenharmony_ciimport queue
127db96d56Sopenharmony_ciimport sys
137db96d56Sopenharmony_ciimport textwrap
147db96d56Sopenharmony_ciimport time
157db96d56Sopenharmony_ciimport traceback
167db96d56Sopenharmony_ciimport _thread as thread
177db96d56Sopenharmony_ciimport threading
187db96d56Sopenharmony_ciimport warnings
197db96d56Sopenharmony_ci
207db96d56Sopenharmony_ciimport idlelib  # testing
217db96d56Sopenharmony_cifrom idlelib import autocomplete  # AutoComplete, fetch_encodings
227db96d56Sopenharmony_cifrom idlelib import calltip  # Calltip
237db96d56Sopenharmony_cifrom idlelib import debugger_r  # start_debugger
247db96d56Sopenharmony_cifrom idlelib import debugobj_r  # remote_object_tree_item
257db96d56Sopenharmony_cifrom idlelib import iomenu  # encoding
267db96d56Sopenharmony_cifrom idlelib import rpc  # multiple objects
277db96d56Sopenharmony_cifrom idlelib import stackviewer  # StackTreeItem
287db96d56Sopenharmony_ciimport __main__
297db96d56Sopenharmony_ci
307db96d56Sopenharmony_ciimport tkinter  # Use tcl and, if startup fails, messagebox.
317db96d56Sopenharmony_ciif not hasattr(sys.modules['idlelib.run'], 'firstrun'):
327db96d56Sopenharmony_ci    # Undo modifications of tkinter by idlelib imports; see bpo-25507.
337db96d56Sopenharmony_ci    for mod in ('simpledialog', 'messagebox', 'font',
347db96d56Sopenharmony_ci                'dialog', 'filedialog', 'commondialog',
357db96d56Sopenharmony_ci                'ttk'):
367db96d56Sopenharmony_ci        delattr(tkinter, mod)
377db96d56Sopenharmony_ci        del sys.modules['tkinter.' + mod]
387db96d56Sopenharmony_ci    # Avoid AttributeError if run again; see bpo-37038.
397db96d56Sopenharmony_ci    sys.modules['idlelib.run'].firstrun = False
407db96d56Sopenharmony_ci
417db96d56Sopenharmony_ciLOCALHOST = '127.0.0.1'
427db96d56Sopenharmony_ci
437db96d56Sopenharmony_citry:
447db96d56Sopenharmony_ci    eof = 'Ctrl-D (end-of-file)'
457db96d56Sopenharmony_ci    exit.eof = eof
467db96d56Sopenharmony_ci    quit.eof = eof
477db96d56Sopenharmony_ciexcept NameError: # In case subprocess started with -S (maybe in future).
487db96d56Sopenharmony_ci    pass
497db96d56Sopenharmony_ci
507db96d56Sopenharmony_ci
517db96d56Sopenharmony_cidef idle_formatwarning(message, category, filename, lineno, line=None):
527db96d56Sopenharmony_ci    """Format warnings the IDLE way."""
537db96d56Sopenharmony_ci
547db96d56Sopenharmony_ci    s = "\nWarning (from warnings module):\n"
557db96d56Sopenharmony_ci    s += f'  File \"{filename}\", line {lineno}\n'
567db96d56Sopenharmony_ci    if line is None:
577db96d56Sopenharmony_ci        line = linecache.getline(filename, lineno)
587db96d56Sopenharmony_ci    line = line.strip()
597db96d56Sopenharmony_ci    if line:
607db96d56Sopenharmony_ci        s += "    %s\n" % line
617db96d56Sopenharmony_ci    s += f"{category.__name__}: {message}\n"
627db96d56Sopenharmony_ci    return s
637db96d56Sopenharmony_ci
647db96d56Sopenharmony_cidef idle_showwarning_subproc(
657db96d56Sopenharmony_ci        message, category, filename, lineno, file=None, line=None):
667db96d56Sopenharmony_ci    """Show Idle-format warning after replacing warnings.showwarning.
677db96d56Sopenharmony_ci
687db96d56Sopenharmony_ci    The only difference is the formatter called.
697db96d56Sopenharmony_ci    """
707db96d56Sopenharmony_ci    if file is None:
717db96d56Sopenharmony_ci        file = sys.stderr
727db96d56Sopenharmony_ci    try:
737db96d56Sopenharmony_ci        file.write(idle_formatwarning(
747db96d56Sopenharmony_ci                message, category, filename, lineno, line))
757db96d56Sopenharmony_ci    except OSError:
767db96d56Sopenharmony_ci        pass # the file (probably stderr) is invalid - this warning gets lost.
777db96d56Sopenharmony_ci
787db96d56Sopenharmony_ci_warnings_showwarning = None
797db96d56Sopenharmony_ci
807db96d56Sopenharmony_cidef capture_warnings(capture):
817db96d56Sopenharmony_ci    "Replace warning.showwarning with idle_showwarning_subproc, or reverse."
827db96d56Sopenharmony_ci
837db96d56Sopenharmony_ci    global _warnings_showwarning
847db96d56Sopenharmony_ci    if capture:
857db96d56Sopenharmony_ci        if _warnings_showwarning is None:
867db96d56Sopenharmony_ci            _warnings_showwarning = warnings.showwarning
877db96d56Sopenharmony_ci            warnings.showwarning = idle_showwarning_subproc
887db96d56Sopenharmony_ci    else:
897db96d56Sopenharmony_ci        if _warnings_showwarning is not None:
907db96d56Sopenharmony_ci            warnings.showwarning = _warnings_showwarning
917db96d56Sopenharmony_ci            _warnings_showwarning = None
927db96d56Sopenharmony_ci
937db96d56Sopenharmony_cicapture_warnings(True)
947db96d56Sopenharmony_citcl = tkinter.Tcl()
957db96d56Sopenharmony_ci
967db96d56Sopenharmony_cidef handle_tk_events(tcl=tcl):
977db96d56Sopenharmony_ci    """Process any tk events that are ready to be dispatched if tkinter
987db96d56Sopenharmony_ci    has been imported, a tcl interpreter has been created and tk has been
997db96d56Sopenharmony_ci    loaded."""
1007db96d56Sopenharmony_ci    tcl.eval("update")
1017db96d56Sopenharmony_ci
1027db96d56Sopenharmony_ci# Thread shared globals: Establish a queue between a subthread (which handles
1037db96d56Sopenharmony_ci# the socket) and the main thread (which runs user code), plus global
1047db96d56Sopenharmony_ci# completion, exit and interruptable (the main thread) flags:
1057db96d56Sopenharmony_ci
1067db96d56Sopenharmony_ciexit_now = False
1077db96d56Sopenharmony_ciquitting = False
1087db96d56Sopenharmony_ciinterruptable = False
1097db96d56Sopenharmony_ci
1107db96d56Sopenharmony_cidef main(del_exitfunc=False):
1117db96d56Sopenharmony_ci    """Start the Python execution server in a subprocess
1127db96d56Sopenharmony_ci
1137db96d56Sopenharmony_ci    In the Python subprocess, RPCServer is instantiated with handlerclass
1147db96d56Sopenharmony_ci    MyHandler, which inherits register/unregister methods from RPCHandler via
1157db96d56Sopenharmony_ci    the mix-in class SocketIO.
1167db96d56Sopenharmony_ci
1177db96d56Sopenharmony_ci    When the RPCServer 'server' is instantiated, the TCPServer initialization
1187db96d56Sopenharmony_ci    creates an instance of run.MyHandler and calls its handle() method.
1197db96d56Sopenharmony_ci    handle() instantiates a run.Executive object, passing it a reference to the
1207db96d56Sopenharmony_ci    MyHandler object.  That reference is saved as attribute rpchandler of the
1217db96d56Sopenharmony_ci    Executive instance.  The Executive methods have access to the reference and
1227db96d56Sopenharmony_ci    can pass it on to entities that they command
1237db96d56Sopenharmony_ci    (e.g. debugger_r.Debugger.start_debugger()).  The latter, in turn, can
1247db96d56Sopenharmony_ci    call MyHandler(SocketIO) register/unregister methods via the reference to
1257db96d56Sopenharmony_ci    register and unregister themselves.
1267db96d56Sopenharmony_ci
1277db96d56Sopenharmony_ci    """
1287db96d56Sopenharmony_ci    global exit_now
1297db96d56Sopenharmony_ci    global quitting
1307db96d56Sopenharmony_ci    global no_exitfunc
1317db96d56Sopenharmony_ci    no_exitfunc = del_exitfunc
1327db96d56Sopenharmony_ci    #time.sleep(15) # test subprocess not responding
1337db96d56Sopenharmony_ci    try:
1347db96d56Sopenharmony_ci        assert(len(sys.argv) > 1)
1357db96d56Sopenharmony_ci        port = int(sys.argv[-1])
1367db96d56Sopenharmony_ci    except:
1377db96d56Sopenharmony_ci        print("IDLE Subprocess: no IP port passed in sys.argv.",
1387db96d56Sopenharmony_ci              file=sys.__stderr__)
1397db96d56Sopenharmony_ci        return
1407db96d56Sopenharmony_ci
1417db96d56Sopenharmony_ci    capture_warnings(True)
1427db96d56Sopenharmony_ci    sys.argv[:] = [""]
1437db96d56Sopenharmony_ci    sockthread = threading.Thread(target=manage_socket,
1447db96d56Sopenharmony_ci                                  name='SockThread',
1457db96d56Sopenharmony_ci                                  args=((LOCALHOST, port),))
1467db96d56Sopenharmony_ci    sockthread.daemon = True
1477db96d56Sopenharmony_ci    sockthread.start()
1487db96d56Sopenharmony_ci    while True:
1497db96d56Sopenharmony_ci        try:
1507db96d56Sopenharmony_ci            if exit_now:
1517db96d56Sopenharmony_ci                try:
1527db96d56Sopenharmony_ci                    exit()
1537db96d56Sopenharmony_ci                except KeyboardInterrupt:
1547db96d56Sopenharmony_ci                    # exiting but got an extra KBI? Try again!
1557db96d56Sopenharmony_ci                    continue
1567db96d56Sopenharmony_ci            try:
1577db96d56Sopenharmony_ci                request = rpc.request_queue.get(block=True, timeout=0.05)
1587db96d56Sopenharmony_ci            except queue.Empty:
1597db96d56Sopenharmony_ci                request = None
1607db96d56Sopenharmony_ci                # Issue 32207: calling handle_tk_events here adds spurious
1617db96d56Sopenharmony_ci                # queue.Empty traceback to event handling exceptions.
1627db96d56Sopenharmony_ci            if request:
1637db96d56Sopenharmony_ci                seq, (method, args, kwargs) = request
1647db96d56Sopenharmony_ci                ret = method(*args, **kwargs)
1657db96d56Sopenharmony_ci                rpc.response_queue.put((seq, ret))
1667db96d56Sopenharmony_ci            else:
1677db96d56Sopenharmony_ci                handle_tk_events()
1687db96d56Sopenharmony_ci        except KeyboardInterrupt:
1697db96d56Sopenharmony_ci            if quitting:
1707db96d56Sopenharmony_ci                exit_now = True
1717db96d56Sopenharmony_ci            continue
1727db96d56Sopenharmony_ci        except SystemExit:
1737db96d56Sopenharmony_ci            capture_warnings(False)
1747db96d56Sopenharmony_ci            raise
1757db96d56Sopenharmony_ci        except:
1767db96d56Sopenharmony_ci            type, value, tb = sys.exc_info()
1777db96d56Sopenharmony_ci            try:
1787db96d56Sopenharmony_ci                print_exception()
1797db96d56Sopenharmony_ci                rpc.response_queue.put((seq, None))
1807db96d56Sopenharmony_ci            except:
1817db96d56Sopenharmony_ci                # Link didn't work, print same exception to __stderr__
1827db96d56Sopenharmony_ci                traceback.print_exception(type, value, tb, file=sys.__stderr__)
1837db96d56Sopenharmony_ci                exit()
1847db96d56Sopenharmony_ci            else:
1857db96d56Sopenharmony_ci                continue
1867db96d56Sopenharmony_ci
1877db96d56Sopenharmony_cidef manage_socket(address):
1887db96d56Sopenharmony_ci    for i in range(3):
1897db96d56Sopenharmony_ci        time.sleep(i)
1907db96d56Sopenharmony_ci        try:
1917db96d56Sopenharmony_ci            server = MyRPCServer(address, MyHandler)
1927db96d56Sopenharmony_ci            break
1937db96d56Sopenharmony_ci        except OSError as err:
1947db96d56Sopenharmony_ci            print("IDLE Subprocess: OSError: " + err.args[1] +
1957db96d56Sopenharmony_ci                  ", retrying....", file=sys.__stderr__)
1967db96d56Sopenharmony_ci            socket_error = err
1977db96d56Sopenharmony_ci    else:
1987db96d56Sopenharmony_ci        print("IDLE Subprocess: Connection to "
1997db96d56Sopenharmony_ci              "IDLE GUI failed, exiting.", file=sys.__stderr__)
2007db96d56Sopenharmony_ci        show_socket_error(socket_error, address)
2017db96d56Sopenharmony_ci        global exit_now
2027db96d56Sopenharmony_ci        exit_now = True
2037db96d56Sopenharmony_ci        return
2047db96d56Sopenharmony_ci    server.handle_request() # A single request only
2057db96d56Sopenharmony_ci
2067db96d56Sopenharmony_cidef show_socket_error(err, address):
2077db96d56Sopenharmony_ci    "Display socket error from manage_socket."
2087db96d56Sopenharmony_ci    import tkinter
2097db96d56Sopenharmony_ci    from tkinter.messagebox import showerror
2107db96d56Sopenharmony_ci    root = tkinter.Tk()
2117db96d56Sopenharmony_ci    fix_scaling(root)
2127db96d56Sopenharmony_ci    root.withdraw()
2137db96d56Sopenharmony_ci    showerror(
2147db96d56Sopenharmony_ci            "Subprocess Connection Error",
2157db96d56Sopenharmony_ci            f"IDLE's subprocess can't connect to {address[0]}:{address[1]}.\n"
2167db96d56Sopenharmony_ci            f"Fatal OSError #{err.errno}: {err.strerror}.\n"
2177db96d56Sopenharmony_ci            "See the 'Startup failure' section of the IDLE doc, online at\n"
2187db96d56Sopenharmony_ci            "https://docs.python.org/3/library/idle.html#startup-failure",
2197db96d56Sopenharmony_ci            parent=root)
2207db96d56Sopenharmony_ci    root.destroy()
2217db96d56Sopenharmony_ci
2227db96d56Sopenharmony_ci
2237db96d56Sopenharmony_cidef get_message_lines(typ, exc, tb):
2247db96d56Sopenharmony_ci    "Return line composing the exception message."
2257db96d56Sopenharmony_ci    if typ in (AttributeError, NameError):
2267db96d56Sopenharmony_ci        # 3.10+ hints are not directly accessible from python (#44026).
2277db96d56Sopenharmony_ci        err = io.StringIO()
2287db96d56Sopenharmony_ci        with contextlib.redirect_stderr(err):
2297db96d56Sopenharmony_ci            sys.__excepthook__(typ, exc, tb)
2307db96d56Sopenharmony_ci        return [err.getvalue().split("\n")[-2] + "\n"]
2317db96d56Sopenharmony_ci    else:
2327db96d56Sopenharmony_ci        return traceback.format_exception_only(typ, exc)
2337db96d56Sopenharmony_ci
2347db96d56Sopenharmony_ci
2357db96d56Sopenharmony_cidef print_exception():
2367db96d56Sopenharmony_ci    import linecache
2377db96d56Sopenharmony_ci    linecache.checkcache()
2387db96d56Sopenharmony_ci    flush_stdout()
2397db96d56Sopenharmony_ci    efile = sys.stderr
2407db96d56Sopenharmony_ci    typ, val, tb = excinfo = sys.exc_info()
2417db96d56Sopenharmony_ci    sys.last_type, sys.last_value, sys.last_traceback = excinfo
2427db96d56Sopenharmony_ci    seen = set()
2437db96d56Sopenharmony_ci
2447db96d56Sopenharmony_ci    def print_exc(typ, exc, tb):
2457db96d56Sopenharmony_ci        seen.add(id(exc))
2467db96d56Sopenharmony_ci        context = exc.__context__
2477db96d56Sopenharmony_ci        cause = exc.__cause__
2487db96d56Sopenharmony_ci        if cause is not None and id(cause) not in seen:
2497db96d56Sopenharmony_ci            print_exc(type(cause), cause, cause.__traceback__)
2507db96d56Sopenharmony_ci            print("\nThe above exception was the direct cause "
2517db96d56Sopenharmony_ci                  "of the following exception:\n", file=efile)
2527db96d56Sopenharmony_ci        elif (context is not None and
2537db96d56Sopenharmony_ci              not exc.__suppress_context__ and
2547db96d56Sopenharmony_ci              id(context) not in seen):
2557db96d56Sopenharmony_ci            print_exc(type(context), context, context.__traceback__)
2567db96d56Sopenharmony_ci            print("\nDuring handling of the above exception, "
2577db96d56Sopenharmony_ci                  "another exception occurred:\n", file=efile)
2587db96d56Sopenharmony_ci        if tb:
2597db96d56Sopenharmony_ci            tbe = traceback.extract_tb(tb)
2607db96d56Sopenharmony_ci            print('Traceback (most recent call last):', file=efile)
2617db96d56Sopenharmony_ci            exclude = ("run.py", "rpc.py", "threading.py", "queue.py",
2627db96d56Sopenharmony_ci                       "debugger_r.py", "bdb.py")
2637db96d56Sopenharmony_ci            cleanup_traceback(tbe, exclude)
2647db96d56Sopenharmony_ci            traceback.print_list(tbe, file=efile)
2657db96d56Sopenharmony_ci        lines = get_message_lines(typ, exc, tb)
2667db96d56Sopenharmony_ci        for line in lines:
2677db96d56Sopenharmony_ci            print(line, end='', file=efile)
2687db96d56Sopenharmony_ci
2697db96d56Sopenharmony_ci    print_exc(typ, val, tb)
2707db96d56Sopenharmony_ci
2717db96d56Sopenharmony_cidef cleanup_traceback(tb, exclude):
2727db96d56Sopenharmony_ci    "Remove excluded traces from beginning/end of tb; get cached lines"
2737db96d56Sopenharmony_ci    orig_tb = tb[:]
2747db96d56Sopenharmony_ci    while tb:
2757db96d56Sopenharmony_ci        for rpcfile in exclude:
2767db96d56Sopenharmony_ci            if tb[0][0].count(rpcfile):
2777db96d56Sopenharmony_ci                break    # found an exclude, break for: and delete tb[0]
2787db96d56Sopenharmony_ci        else:
2797db96d56Sopenharmony_ci            break        # no excludes, have left RPC code, break while:
2807db96d56Sopenharmony_ci        del tb[0]
2817db96d56Sopenharmony_ci    while tb:
2827db96d56Sopenharmony_ci        for rpcfile in exclude:
2837db96d56Sopenharmony_ci            if tb[-1][0].count(rpcfile):
2847db96d56Sopenharmony_ci                break
2857db96d56Sopenharmony_ci        else:
2867db96d56Sopenharmony_ci            break
2877db96d56Sopenharmony_ci        del tb[-1]
2887db96d56Sopenharmony_ci    if len(tb) == 0:
2897db96d56Sopenharmony_ci        # exception was in IDLE internals, don't prune!
2907db96d56Sopenharmony_ci        tb[:] = orig_tb[:]
2917db96d56Sopenharmony_ci        print("** IDLE Internal Exception: ", file=sys.stderr)
2927db96d56Sopenharmony_ci    rpchandler = rpc.objecttable['exec'].rpchandler
2937db96d56Sopenharmony_ci    for i in range(len(tb)):
2947db96d56Sopenharmony_ci        fn, ln, nm, line = tb[i]
2957db96d56Sopenharmony_ci        if nm == '?':
2967db96d56Sopenharmony_ci            nm = "-toplevel-"
2977db96d56Sopenharmony_ci        if not line and fn.startswith("<pyshell#"):
2987db96d56Sopenharmony_ci            line = rpchandler.remotecall('linecache', 'getline',
2997db96d56Sopenharmony_ci                                              (fn, ln), {})
3007db96d56Sopenharmony_ci        tb[i] = fn, ln, nm, line
3017db96d56Sopenharmony_ci
3027db96d56Sopenharmony_cidef flush_stdout():
3037db96d56Sopenharmony_ci    """XXX How to do this now?"""
3047db96d56Sopenharmony_ci
3057db96d56Sopenharmony_cidef exit():
3067db96d56Sopenharmony_ci    """Exit subprocess, possibly after first clearing exit functions.
3077db96d56Sopenharmony_ci
3087db96d56Sopenharmony_ci    If config-main.cfg/.def 'General' 'delete-exitfunc' is True, then any
3097db96d56Sopenharmony_ci    functions registered with atexit will be removed before exiting.
3107db96d56Sopenharmony_ci    (VPython support)
3117db96d56Sopenharmony_ci
3127db96d56Sopenharmony_ci    """
3137db96d56Sopenharmony_ci    if no_exitfunc:
3147db96d56Sopenharmony_ci        import atexit
3157db96d56Sopenharmony_ci        atexit._clear()
3167db96d56Sopenharmony_ci    capture_warnings(False)
3177db96d56Sopenharmony_ci    sys.exit(0)
3187db96d56Sopenharmony_ci
3197db96d56Sopenharmony_ci
3207db96d56Sopenharmony_cidef fix_scaling(root):
3217db96d56Sopenharmony_ci    """Scale fonts on HiDPI displays."""
3227db96d56Sopenharmony_ci    import tkinter.font
3237db96d56Sopenharmony_ci    scaling = float(root.tk.call('tk', 'scaling'))
3247db96d56Sopenharmony_ci    if scaling > 1.4:
3257db96d56Sopenharmony_ci        for name in tkinter.font.names(root):
3267db96d56Sopenharmony_ci            font = tkinter.font.Font(root=root, name=name, exists=True)
3277db96d56Sopenharmony_ci            size = int(font['size'])
3287db96d56Sopenharmony_ci            if size < 0:
3297db96d56Sopenharmony_ci                font['size'] = round(-0.75*size)
3307db96d56Sopenharmony_ci
3317db96d56Sopenharmony_ci
3327db96d56Sopenharmony_cidef fixdoc(fun, text):
3337db96d56Sopenharmony_ci    tem = (fun.__doc__ + '\n\n') if fun.__doc__ is not None else ''
3347db96d56Sopenharmony_ci    fun.__doc__ = tem + textwrap.fill(textwrap.dedent(text))
3357db96d56Sopenharmony_ci
3367db96d56Sopenharmony_ciRECURSIONLIMIT_DELTA = 30
3377db96d56Sopenharmony_ci
3387db96d56Sopenharmony_cidef install_recursionlimit_wrappers():
3397db96d56Sopenharmony_ci    """Install wrappers to always add 30 to the recursion limit."""
3407db96d56Sopenharmony_ci    # see: bpo-26806
3417db96d56Sopenharmony_ci
3427db96d56Sopenharmony_ci    @functools.wraps(sys.setrecursionlimit)
3437db96d56Sopenharmony_ci    def setrecursionlimit(*args, **kwargs):
3447db96d56Sopenharmony_ci        # mimic the original sys.setrecursionlimit()'s input handling
3457db96d56Sopenharmony_ci        if kwargs:
3467db96d56Sopenharmony_ci            raise TypeError(
3477db96d56Sopenharmony_ci                "setrecursionlimit() takes no keyword arguments")
3487db96d56Sopenharmony_ci        try:
3497db96d56Sopenharmony_ci            limit, = args
3507db96d56Sopenharmony_ci        except ValueError:
3517db96d56Sopenharmony_ci            raise TypeError(f"setrecursionlimit() takes exactly one "
3527db96d56Sopenharmony_ci                            f"argument ({len(args)} given)")
3537db96d56Sopenharmony_ci        if not limit > 0:
3547db96d56Sopenharmony_ci            raise ValueError(
3557db96d56Sopenharmony_ci                "recursion limit must be greater or equal than 1")
3567db96d56Sopenharmony_ci
3577db96d56Sopenharmony_ci        return setrecursionlimit.__wrapped__(limit + RECURSIONLIMIT_DELTA)
3587db96d56Sopenharmony_ci
3597db96d56Sopenharmony_ci    fixdoc(setrecursionlimit, f"""\
3607db96d56Sopenharmony_ci            This IDLE wrapper adds {RECURSIONLIMIT_DELTA} to prevent possible
3617db96d56Sopenharmony_ci            uninterruptible loops.""")
3627db96d56Sopenharmony_ci
3637db96d56Sopenharmony_ci    @functools.wraps(sys.getrecursionlimit)
3647db96d56Sopenharmony_ci    def getrecursionlimit():
3657db96d56Sopenharmony_ci        return getrecursionlimit.__wrapped__() - RECURSIONLIMIT_DELTA
3667db96d56Sopenharmony_ci
3677db96d56Sopenharmony_ci    fixdoc(getrecursionlimit, f"""\
3687db96d56Sopenharmony_ci            This IDLE wrapper subtracts {RECURSIONLIMIT_DELTA} to compensate
3697db96d56Sopenharmony_ci            for the {RECURSIONLIMIT_DELTA} IDLE adds when setting the limit.""")
3707db96d56Sopenharmony_ci
3717db96d56Sopenharmony_ci    # add the delta to the default recursion limit, to compensate
3727db96d56Sopenharmony_ci    sys.setrecursionlimit(sys.getrecursionlimit() + RECURSIONLIMIT_DELTA)
3737db96d56Sopenharmony_ci
3747db96d56Sopenharmony_ci    sys.setrecursionlimit = setrecursionlimit
3757db96d56Sopenharmony_ci    sys.getrecursionlimit = getrecursionlimit
3767db96d56Sopenharmony_ci
3777db96d56Sopenharmony_ci
3787db96d56Sopenharmony_cidef uninstall_recursionlimit_wrappers():
3797db96d56Sopenharmony_ci    """Uninstall the recursion limit wrappers from the sys module.
3807db96d56Sopenharmony_ci
3817db96d56Sopenharmony_ci    IDLE only uses this for tests. Users can import run and call
3827db96d56Sopenharmony_ci    this to remove the wrapping.
3837db96d56Sopenharmony_ci    """
3847db96d56Sopenharmony_ci    if (
3857db96d56Sopenharmony_ci            getattr(sys.setrecursionlimit, '__wrapped__', None) and
3867db96d56Sopenharmony_ci            getattr(sys.getrecursionlimit, '__wrapped__', None)
3877db96d56Sopenharmony_ci    ):
3887db96d56Sopenharmony_ci        sys.setrecursionlimit = sys.setrecursionlimit.__wrapped__
3897db96d56Sopenharmony_ci        sys.getrecursionlimit = sys.getrecursionlimit.__wrapped__
3907db96d56Sopenharmony_ci        sys.setrecursionlimit(sys.getrecursionlimit() - RECURSIONLIMIT_DELTA)
3917db96d56Sopenharmony_ci
3927db96d56Sopenharmony_ci
3937db96d56Sopenharmony_ciclass MyRPCServer(rpc.RPCServer):
3947db96d56Sopenharmony_ci
3957db96d56Sopenharmony_ci    def handle_error(self, request, client_address):
3967db96d56Sopenharmony_ci        """Override RPCServer method for IDLE
3977db96d56Sopenharmony_ci
3987db96d56Sopenharmony_ci        Interrupt the MainThread and exit server if link is dropped.
3997db96d56Sopenharmony_ci
4007db96d56Sopenharmony_ci        """
4017db96d56Sopenharmony_ci        global quitting
4027db96d56Sopenharmony_ci        try:
4037db96d56Sopenharmony_ci            raise
4047db96d56Sopenharmony_ci        except SystemExit:
4057db96d56Sopenharmony_ci            raise
4067db96d56Sopenharmony_ci        except EOFError:
4077db96d56Sopenharmony_ci            global exit_now
4087db96d56Sopenharmony_ci            exit_now = True
4097db96d56Sopenharmony_ci            thread.interrupt_main()
4107db96d56Sopenharmony_ci        except:
4117db96d56Sopenharmony_ci            erf = sys.__stderr__
4127db96d56Sopenharmony_ci            print(textwrap.dedent(f"""
4137db96d56Sopenharmony_ci            {'-'*40}
4147db96d56Sopenharmony_ci            Unhandled exception in user code execution server!'
4157db96d56Sopenharmony_ci            Thread: {threading.current_thread().name}
4167db96d56Sopenharmony_ci            IDLE Client Address: {client_address}
4177db96d56Sopenharmony_ci            Request: {request!r}
4187db96d56Sopenharmony_ci            """), file=erf)
4197db96d56Sopenharmony_ci            traceback.print_exc(limit=-20, file=erf)
4207db96d56Sopenharmony_ci            print(textwrap.dedent(f"""
4217db96d56Sopenharmony_ci            *** Unrecoverable, server exiting!
4227db96d56Sopenharmony_ci
4237db96d56Sopenharmony_ci            Users should never see this message; it is likely transient.
4247db96d56Sopenharmony_ci            If this recurs, report this with a copy of the message
4257db96d56Sopenharmony_ci            and an explanation of how to make it repeat.
4267db96d56Sopenharmony_ci            {'-'*40}"""), file=erf)
4277db96d56Sopenharmony_ci            quitting = True
4287db96d56Sopenharmony_ci            thread.interrupt_main()
4297db96d56Sopenharmony_ci
4307db96d56Sopenharmony_ci
4317db96d56Sopenharmony_ci# Pseudofiles for shell-remote communication (also used in pyshell)
4327db96d56Sopenharmony_ci
4337db96d56Sopenharmony_ciclass StdioFile(io.TextIOBase):
4347db96d56Sopenharmony_ci
4357db96d56Sopenharmony_ci    def __init__(self, shell, tags, encoding='utf-8', errors='strict'):
4367db96d56Sopenharmony_ci        self.shell = shell
4377db96d56Sopenharmony_ci        self.tags = tags
4387db96d56Sopenharmony_ci        self._encoding = encoding
4397db96d56Sopenharmony_ci        self._errors = errors
4407db96d56Sopenharmony_ci
4417db96d56Sopenharmony_ci    @property
4427db96d56Sopenharmony_ci    def encoding(self):
4437db96d56Sopenharmony_ci        return self._encoding
4447db96d56Sopenharmony_ci
4457db96d56Sopenharmony_ci    @property
4467db96d56Sopenharmony_ci    def errors(self):
4477db96d56Sopenharmony_ci        return self._errors
4487db96d56Sopenharmony_ci
4497db96d56Sopenharmony_ci    @property
4507db96d56Sopenharmony_ci    def name(self):
4517db96d56Sopenharmony_ci        return '<%s>' % self.tags
4527db96d56Sopenharmony_ci
4537db96d56Sopenharmony_ci    def isatty(self):
4547db96d56Sopenharmony_ci        return True
4557db96d56Sopenharmony_ci
4567db96d56Sopenharmony_ci
4577db96d56Sopenharmony_ciclass StdOutputFile(StdioFile):
4587db96d56Sopenharmony_ci
4597db96d56Sopenharmony_ci    def writable(self):
4607db96d56Sopenharmony_ci        return True
4617db96d56Sopenharmony_ci
4627db96d56Sopenharmony_ci    def write(self, s):
4637db96d56Sopenharmony_ci        if self.closed:
4647db96d56Sopenharmony_ci            raise ValueError("write to closed file")
4657db96d56Sopenharmony_ci        s = str.encode(s, self.encoding, self.errors).decode(self.encoding, self.errors)
4667db96d56Sopenharmony_ci        return self.shell.write(s, self.tags)
4677db96d56Sopenharmony_ci
4687db96d56Sopenharmony_ci
4697db96d56Sopenharmony_ciclass StdInputFile(StdioFile):
4707db96d56Sopenharmony_ci    _line_buffer = ''
4717db96d56Sopenharmony_ci
4727db96d56Sopenharmony_ci    def readable(self):
4737db96d56Sopenharmony_ci        return True
4747db96d56Sopenharmony_ci
4757db96d56Sopenharmony_ci    def read(self, size=-1):
4767db96d56Sopenharmony_ci        if self.closed:
4777db96d56Sopenharmony_ci            raise ValueError("read from closed file")
4787db96d56Sopenharmony_ci        if size is None:
4797db96d56Sopenharmony_ci            size = -1
4807db96d56Sopenharmony_ci        elif not isinstance(size, int):
4817db96d56Sopenharmony_ci            raise TypeError('must be int, not ' + type(size).__name__)
4827db96d56Sopenharmony_ci        result = self._line_buffer
4837db96d56Sopenharmony_ci        self._line_buffer = ''
4847db96d56Sopenharmony_ci        if size < 0:
4857db96d56Sopenharmony_ci            while line := self.shell.readline():
4867db96d56Sopenharmony_ci                result += line
4877db96d56Sopenharmony_ci        else:
4887db96d56Sopenharmony_ci            while len(result) < size:
4897db96d56Sopenharmony_ci                line = self.shell.readline()
4907db96d56Sopenharmony_ci                if not line: break
4917db96d56Sopenharmony_ci                result += line
4927db96d56Sopenharmony_ci            self._line_buffer = result[size:]
4937db96d56Sopenharmony_ci            result = result[:size]
4947db96d56Sopenharmony_ci        return result
4957db96d56Sopenharmony_ci
4967db96d56Sopenharmony_ci    def readline(self, size=-1):
4977db96d56Sopenharmony_ci        if self.closed:
4987db96d56Sopenharmony_ci            raise ValueError("read from closed file")
4997db96d56Sopenharmony_ci        if size is None:
5007db96d56Sopenharmony_ci            size = -1
5017db96d56Sopenharmony_ci        elif not isinstance(size, int):
5027db96d56Sopenharmony_ci            raise TypeError('must be int, not ' + type(size).__name__)
5037db96d56Sopenharmony_ci        line = self._line_buffer or self.shell.readline()
5047db96d56Sopenharmony_ci        if size < 0:
5057db96d56Sopenharmony_ci            size = len(line)
5067db96d56Sopenharmony_ci        eol = line.find('\n', 0, size)
5077db96d56Sopenharmony_ci        if eol >= 0:
5087db96d56Sopenharmony_ci            size = eol + 1
5097db96d56Sopenharmony_ci        self._line_buffer = line[size:]
5107db96d56Sopenharmony_ci        return line[:size]
5117db96d56Sopenharmony_ci
5127db96d56Sopenharmony_ci    def close(self):
5137db96d56Sopenharmony_ci        self.shell.close()
5147db96d56Sopenharmony_ci
5157db96d56Sopenharmony_ci
5167db96d56Sopenharmony_ciclass MyHandler(rpc.RPCHandler):
5177db96d56Sopenharmony_ci
5187db96d56Sopenharmony_ci    def handle(self):
5197db96d56Sopenharmony_ci        """Override base method"""
5207db96d56Sopenharmony_ci        executive = Executive(self)
5217db96d56Sopenharmony_ci        self.register("exec", executive)
5227db96d56Sopenharmony_ci        self.console = self.get_remote_proxy("console")
5237db96d56Sopenharmony_ci        sys.stdin = StdInputFile(self.console, "stdin",
5247db96d56Sopenharmony_ci                                 iomenu.encoding, iomenu.errors)
5257db96d56Sopenharmony_ci        sys.stdout = StdOutputFile(self.console, "stdout",
5267db96d56Sopenharmony_ci                                   iomenu.encoding, iomenu.errors)
5277db96d56Sopenharmony_ci        sys.stderr = StdOutputFile(self.console, "stderr",
5287db96d56Sopenharmony_ci                                   iomenu.encoding, "backslashreplace")
5297db96d56Sopenharmony_ci
5307db96d56Sopenharmony_ci        sys.displayhook = rpc.displayhook
5317db96d56Sopenharmony_ci        # page help() text to shell.
5327db96d56Sopenharmony_ci        import pydoc # import must be done here to capture i/o binding
5337db96d56Sopenharmony_ci        pydoc.pager = pydoc.plainpager
5347db96d56Sopenharmony_ci
5357db96d56Sopenharmony_ci        # Keep a reference to stdin so that it won't try to exit IDLE if
5367db96d56Sopenharmony_ci        # sys.stdin gets changed from within IDLE's shell. See issue17838.
5377db96d56Sopenharmony_ci        self._keep_stdin = sys.stdin
5387db96d56Sopenharmony_ci
5397db96d56Sopenharmony_ci        install_recursionlimit_wrappers()
5407db96d56Sopenharmony_ci
5417db96d56Sopenharmony_ci        self.interp = self.get_remote_proxy("interp")
5427db96d56Sopenharmony_ci        rpc.RPCHandler.getresponse(self, myseq=None, wait=0.05)
5437db96d56Sopenharmony_ci
5447db96d56Sopenharmony_ci    def exithook(self):
5457db96d56Sopenharmony_ci        "override SocketIO method - wait for MainThread to shut us down"
5467db96d56Sopenharmony_ci        time.sleep(10)
5477db96d56Sopenharmony_ci
5487db96d56Sopenharmony_ci    def EOFhook(self):
5497db96d56Sopenharmony_ci        "Override SocketIO method - terminate wait on callback and exit thread"
5507db96d56Sopenharmony_ci        global quitting
5517db96d56Sopenharmony_ci        quitting = True
5527db96d56Sopenharmony_ci        thread.interrupt_main()
5537db96d56Sopenharmony_ci
5547db96d56Sopenharmony_ci    def decode_interrupthook(self):
5557db96d56Sopenharmony_ci        "interrupt awakened thread"
5567db96d56Sopenharmony_ci        global quitting
5577db96d56Sopenharmony_ci        quitting = True
5587db96d56Sopenharmony_ci        thread.interrupt_main()
5597db96d56Sopenharmony_ci
5607db96d56Sopenharmony_ci
5617db96d56Sopenharmony_ciclass Executive:
5627db96d56Sopenharmony_ci
5637db96d56Sopenharmony_ci    def __init__(self, rpchandler):
5647db96d56Sopenharmony_ci        self.rpchandler = rpchandler
5657db96d56Sopenharmony_ci        if idlelib.testing is False:
5667db96d56Sopenharmony_ci            self.locals = __main__.__dict__
5677db96d56Sopenharmony_ci            self.calltip = calltip.Calltip()
5687db96d56Sopenharmony_ci            self.autocomplete = autocomplete.AutoComplete()
5697db96d56Sopenharmony_ci        else:
5707db96d56Sopenharmony_ci            self.locals = {}
5717db96d56Sopenharmony_ci
5727db96d56Sopenharmony_ci    def runcode(self, code):
5737db96d56Sopenharmony_ci        global interruptable
5747db96d56Sopenharmony_ci        try:
5757db96d56Sopenharmony_ci            self.user_exc_info = None
5767db96d56Sopenharmony_ci            interruptable = True
5777db96d56Sopenharmony_ci            try:
5787db96d56Sopenharmony_ci                exec(code, self.locals)
5797db96d56Sopenharmony_ci            finally:
5807db96d56Sopenharmony_ci                interruptable = False
5817db96d56Sopenharmony_ci        except SystemExit as e:
5827db96d56Sopenharmony_ci            if e.args:  # SystemExit called with an argument.
5837db96d56Sopenharmony_ci                ob = e.args[0]
5847db96d56Sopenharmony_ci                if not isinstance(ob, (type(None), int)):
5857db96d56Sopenharmony_ci                    print('SystemExit: ' + str(ob), file=sys.stderr)
5867db96d56Sopenharmony_ci            # Return to the interactive prompt.
5877db96d56Sopenharmony_ci        except:
5887db96d56Sopenharmony_ci            self.user_exc_info = sys.exc_info()  # For testing, hook, viewer.
5897db96d56Sopenharmony_ci            if quitting:
5907db96d56Sopenharmony_ci                exit()
5917db96d56Sopenharmony_ci            if sys.excepthook is sys.__excepthook__:
5927db96d56Sopenharmony_ci                print_exception()
5937db96d56Sopenharmony_ci            else:
5947db96d56Sopenharmony_ci                try:
5957db96d56Sopenharmony_ci                    sys.excepthook(*self.user_exc_info)
5967db96d56Sopenharmony_ci                except:
5977db96d56Sopenharmony_ci                    self.user_exc_info = sys.exc_info()  # For testing.
5987db96d56Sopenharmony_ci                    print_exception()
5997db96d56Sopenharmony_ci            jit = self.rpchandler.console.getvar("<<toggle-jit-stack-viewer>>")
6007db96d56Sopenharmony_ci            if jit:
6017db96d56Sopenharmony_ci                self.rpchandler.interp.open_remote_stack_viewer()
6027db96d56Sopenharmony_ci        else:
6037db96d56Sopenharmony_ci            flush_stdout()
6047db96d56Sopenharmony_ci
6057db96d56Sopenharmony_ci    def interrupt_the_server(self):
6067db96d56Sopenharmony_ci        if interruptable:
6077db96d56Sopenharmony_ci            thread.interrupt_main()
6087db96d56Sopenharmony_ci
6097db96d56Sopenharmony_ci    def start_the_debugger(self, gui_adap_oid):
6107db96d56Sopenharmony_ci        return debugger_r.start_debugger(self.rpchandler, gui_adap_oid)
6117db96d56Sopenharmony_ci
6127db96d56Sopenharmony_ci    def stop_the_debugger(self, idb_adap_oid):
6137db96d56Sopenharmony_ci        "Unregister the Idb Adapter.  Link objects and Idb then subject to GC"
6147db96d56Sopenharmony_ci        self.rpchandler.unregister(idb_adap_oid)
6157db96d56Sopenharmony_ci
6167db96d56Sopenharmony_ci    def get_the_calltip(self, name):
6177db96d56Sopenharmony_ci        return self.calltip.fetch_tip(name)
6187db96d56Sopenharmony_ci
6197db96d56Sopenharmony_ci    def get_the_completion_list(self, what, mode):
6207db96d56Sopenharmony_ci        return self.autocomplete.fetch_completions(what, mode)
6217db96d56Sopenharmony_ci
6227db96d56Sopenharmony_ci    def stackviewer(self, flist_oid=None):
6237db96d56Sopenharmony_ci        if self.user_exc_info:
6247db96d56Sopenharmony_ci            typ, val, tb = self.user_exc_info
6257db96d56Sopenharmony_ci        else:
6267db96d56Sopenharmony_ci            return None
6277db96d56Sopenharmony_ci        flist = None
6287db96d56Sopenharmony_ci        if flist_oid is not None:
6297db96d56Sopenharmony_ci            flist = self.rpchandler.get_remote_proxy(flist_oid)
6307db96d56Sopenharmony_ci        while tb and tb.tb_frame.f_globals["__name__"] in ["rpc", "run"]:
6317db96d56Sopenharmony_ci            tb = tb.tb_next
6327db96d56Sopenharmony_ci        sys.last_type = typ
6337db96d56Sopenharmony_ci        sys.last_value = val
6347db96d56Sopenharmony_ci        item = stackviewer.StackTreeItem(flist, tb)
6357db96d56Sopenharmony_ci        return debugobj_r.remote_object_tree_item(item)
6367db96d56Sopenharmony_ci
6377db96d56Sopenharmony_ci
6387db96d56Sopenharmony_ciif __name__ == '__main__':
6397db96d56Sopenharmony_ci    from unittest import main
6407db96d56Sopenharmony_ci    main('idlelib.idle_test.test_run', verbosity=2)
6417db96d56Sopenharmony_ci
6427db96d56Sopenharmony_cicapture_warnings(False)  # Make sure turned off; see bpo-18081.
643