|
3 | 3 | import ast |
4 | 4 | import inspect |
5 | 5 | import sys |
6 | | -import warnings |
7 | 6 | from code import InteractiveConsole |
8 | | -from signal import SIGINT, raise_signal, signal |
9 | | -from types import CodeType, FrameType, FunctionType |
10 | | -from typing import TYPE_CHECKING |
| 7 | +from signal import SIGINT, raise_signal |
| 8 | +from types import CodeType, FunctionType |
11 | 9 |
|
12 | 10 | import outcome |
13 | 11 |
|
14 | 12 | import trio |
15 | 13 | from trio._util import final |
16 | 14 |
|
17 | | -if TYPE_CHECKING: |
18 | | - from collections.abc import Callable |
19 | | - |
20 | 15 | try: |
21 | 16 | import pyrepl |
22 | 17 | from pyrepl import commands, reader as r, readline |
@@ -47,13 +42,9 @@ class TrioInteractiveConsole(InteractiveConsole): |
47 | 42 | def __init__( # type: ignore[no-any-unimported] |
48 | 43 | self, |
49 | 44 | repl_locals: dict[str, object] | None = None, |
50 | | - reader: r.Reader | None = None, |
51 | 45 | ) -> None: |
52 | 46 | super().__init__(locals=repl_locals) |
53 | 47 | self.compile.compiler.flags |= ast.PyCF_ALLOW_TOP_LEVEL_AWAIT |
54 | | - self.reader = reader |
55 | | - self.trim_first_char = False |
56 | | - self.interrupted = False |
57 | 48 |
|
58 | 49 | def runcode(self, code: CodeType) -> None: |
59 | 50 | func = FunctionType(code, self.locals) |
@@ -87,71 +78,63 @@ def runcode(self, code: CodeType) -> None: |
87 | 78 | trio.from_thread.run(trio.lowlevel.checkpoint_if_cancelled) |
88 | 79 | # trio.from_thread.check_cancelled() has too long of a memory |
89 | 80 |
|
90 | | - def raw_input(self, prompt: str = "") -> str: |
91 | | - def install_handler() -> Callable[[int, FrameType | None], None] | int | None: |
92 | | - def handler(sig: int, frame: FrameType | None) -> None: |
93 | | - self.interrupted = True |
94 | | - |
95 | | - return signal(SIGINT, handler) |
96 | | - |
97 | | - prev_handler = trio.from_thread.run_sync(install_handler) |
98 | 81 |
|
99 | | - assert self.reader is not None |
100 | | - self.reader.ps1 = prompt |
101 | | - self.interrupted = False |
102 | | - self.reader.prepare() |
103 | | - try: |
104 | | - self.reader.refresh() |
105 | | - while not self.reader.finished and not self.interrupted: |
106 | | - if not self.reader.handle1(block=False): |
107 | | - # let's avoid busy waiting |
108 | | - if CPYTHON_VENDOR: |
109 | | - self.reader.console.wait(100) |
110 | | - else: |
111 | | - self.reader.console.pollob.poll(100) |
112 | | - |
113 | | - if self.interrupted: |
114 | | - if not CPYTHON_VENDOR: |
115 | | - self.trim_first_char = True |
116 | | - raise KeyboardInterrupt |
117 | | - if CPYTHON_VENDOR: |
118 | | - return self.reader.get_unicode() # type: ignore[no-any-return] |
119 | | - else: |
120 | | - return self.reader.get_str() # type: ignore[no-any-return] |
121 | | - finally: |
122 | | - trio.from_thread.run_sync(signal, SIGINT, prev_handler) |
123 | | - self.reader.restore() |
| 82 | +async def repl_input(reader: r.Reader, prompt: str) -> str: |
| 83 | + assert reader is not None |
| 84 | + reader.ps1 = prompt |
| 85 | + reader.prepare() |
| 86 | + try: |
| 87 | + reader.refresh() |
| 88 | + while not reader.finished: |
| 89 | + if not reader.handle1(block=False): |
| 90 | + if sys.platform == "win32": |
| 91 | + await trio.lowlevel.wait_readable(pyrepl.windows_console.InHandle) |
| 92 | + else: |
| 93 | + await trio.lowlevel.wait_readable(reader.console.input_fd) |
| 94 | + |
| 95 | + if CPYTHON_VENDOR: |
| 96 | + return reader.get_unicode() # type: ignore[no-any-return] |
| 97 | + else: |
| 98 | + return reader.get_str() # type: ignore[no-any-return] |
| 99 | + finally: |
| 100 | + reader.restore() |
124 | 101 |
|
125 | | - if not CPYTHON_VENDOR: |
126 | | - # pyrepl has some special handling to make sure that |
127 | | - # the console is always ended in `\r\n` when done. |
128 | | - # However, InteractiveConsole assumes that the input |
129 | | - # was exited without a newline! So we need this hack. |
130 | | - def write(self, output: str) -> None: |
131 | | - if self.trim_first_char: |
132 | | - assert output == "\nKeyboardInterrupt\n" |
133 | | - sys.stderr.write(output[1:]) |
134 | | - self.trim_first_char = False |
135 | | - else: |
136 | | - sys.stderr.write(output) |
137 | 102 |
|
| 103 | +async def run_repl(console: TrioInteractiveConsole, reader: r.Reader) -> None: |
| 104 | + # mostly copy-pasted from code.InteractiveConsole.interact |
| 105 | + try: |
| 106 | + sys.ps1 # noqa: B018 |
| 107 | + except AttributeError: |
| 108 | + sys.ps1 = ">>> " |
| 109 | + try: |
| 110 | + sys.ps2 # noqa: B018 |
| 111 | + except AttributeError: |
| 112 | + sys.ps2 = "... " |
138 | 113 |
|
139 | | -async def run_repl(console: TrioInteractiveConsole) -> None: |
140 | 114 | banner = ( |
141 | 115 | f"trio REPL {sys.version} on {sys.platform}\n" |
142 | 116 | f'Use "await" directly instead of "trio.run()".\n' |
143 | 117 | f'Type "help", "copyright", "credits" or "license" ' |
144 | 118 | f"for more information.\n" |
145 | | - f'{getattr(sys, "ps1", ">>> ")}import trio' |
| 119 | + f'{getattr(sys, "ps1", ">>> ")}import trio\n' |
146 | 120 | ) |
147 | | - try: |
148 | | - await trio.to_thread.run_sync(console.interact, banner) |
149 | | - finally: |
150 | | - warnings.filterwarnings( |
151 | | - "ignore", |
152 | | - message=r"^coroutine .* was never awaited$", |
153 | | - category=RuntimeWarning, |
154 | | - ) |
| 121 | + console.write(banner) |
| 122 | + more = 0 |
| 123 | + |
| 124 | + while True: |
| 125 | + try: |
| 126 | + prompt = sys.ps2 if more else sys.ps1 |
| 127 | + try: |
| 128 | + line = await repl_input(reader, prompt) |
| 129 | + except EOFError: |
| 130 | + console.write("\n") |
| 131 | + break |
| 132 | + else: |
| 133 | + more = await trio.to_thread.run_sync(console.push, line) |
| 134 | + except KeyboardInterrupt: |
| 135 | + console.write("\nKeyboardInterrupt\n") |
| 136 | + console.resetbuffer() |
| 137 | + more = 0 |
155 | 138 |
|
156 | 139 |
|
157 | 140 | def main(original_locals: dict[str, object]) -> None: |
@@ -180,5 +163,5 @@ def do(self) -> None: |
180 | 163 |
|
181 | 164 | reader.commands["interrupt"] = interrupt |
182 | 165 |
|
183 | | - console = TrioInteractiveConsole(repl_locals, reader) |
184 | | - trio.run(run_repl, console) |
| 166 | + console = TrioInteractiveConsole(repl_locals) |
| 167 | + trio.run(run_repl, console, reader) |
0 commit comments