Skip to content

Commit 6d670a3

Browse files
committed
Fool around and remove the background thread
1 parent 1287a1b commit 6d670a3

1 file changed

Lines changed: 51 additions & 68 deletions

File tree

src/trio/_repl.py

Lines changed: 51 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,15 @@
33
import ast
44
import inspect
55
import sys
6-
import warnings
76
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
119

1210
import outcome
1311

1412
import trio
1513
from trio._util import final
1614

17-
if TYPE_CHECKING:
18-
from collections.abc import Callable
19-
2015
try:
2116
import pyrepl
2217
from pyrepl import commands, reader as r, readline
@@ -47,13 +42,9 @@ class TrioInteractiveConsole(InteractiveConsole):
4742
def __init__( # type: ignore[no-any-unimported]
4843
self,
4944
repl_locals: dict[str, object] | None = None,
50-
reader: r.Reader | None = None,
5145
) -> None:
5246
super().__init__(locals=repl_locals)
5347
self.compile.compiler.flags |= ast.PyCF_ALLOW_TOP_LEVEL_AWAIT
54-
self.reader = reader
55-
self.trim_first_char = False
56-
self.interrupted = False
5748

5849
def runcode(self, code: CodeType) -> None:
5950
func = FunctionType(code, self.locals)
@@ -87,71 +78,63 @@ def runcode(self, code: CodeType) -> None:
8778
trio.from_thread.run(trio.lowlevel.checkpoint_if_cancelled)
8879
# trio.from_thread.check_cancelled() has too long of a memory
8980

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)
9881

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()
124101

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)
137102

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 = "... "
138113

139-
async def run_repl(console: TrioInteractiveConsole) -> None:
140114
banner = (
141115
f"trio REPL {sys.version} on {sys.platform}\n"
142116
f'Use "await" directly instead of "trio.run()".\n'
143117
f'Type "help", "copyright", "credits" or "license" '
144118
f"for more information.\n"
145-
f'{getattr(sys, "ps1", ">>> ")}import trio'
119+
f'{getattr(sys, "ps1", ">>> ")}import trio\n'
146120
)
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
155138

156139

157140
def main(original_locals: dict[str, object]) -> None:
@@ -180,5 +163,5 @@ def do(self) -> None:
180163

181164
reader.commands["interrupt"] = interrupt
182165

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

Comments
 (0)