Skip to content

gh-152907: Restore cooked output flags around the input hook in the new REPL#153389

Open
harjothkhara wants to merge 4 commits into
python:mainfrom
harjothkhara:gh-152907-pyrepl-inputhook-opost
Open

gh-152907: Restore cooked output flags around the input hook in the new REPL#153389
harjothkhara wants to merge 4 commits into
python:mainfrom
harjothkhara:gh-152907-pyrepl-inputhook-opost

Conversation

@harjothkhara

@harjothkhara harjothkhara commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

The problem: While the new REPL waits for input, it puts the terminal in raw mode, which turns off the usual \n\r\n translation. It also runs input hooks during that wait — these are what GUI toolkits (Tkinter, Qt, …) use to keep their event loops alive at the prompt. So when a hook prints something, each line starts where the previous one ended instead of at the left margin, and the text "staircases" down the screen. The old readline REPL didn't do this, and PYTHON_BASIC_REPL=1 still works fine.

The fix: The REPL needs raw mode for drawing its own output, but the input hook runs at a moment when it isn't drawing. So this turns the newline translation back on just around the hook call, then returns to raw mode. Only the output flags are touched, so keystrokes are still not echoed at the prompt.

Checked with a pty that captures the exact bytes a hook writes:

before:  line1\nline2\nline3\n
after:   line1\r\nline2\r\nline3\r\n

There's a new test that fails without the fix, and ./python -m test test_pyrepl passes.

I used AI assistance for this and have reviewed the change.

Refs #152907

… the new REPL

pyrepl clears OPOST for its own cursor rendering but calls PyOS_InputHook
from inside the raw-mode read loop, so output written by an input hook
(GUI toolkit event loops, and any warning/traceback/print they emit) is
emitted with bare '\n' and no '\r'.  Restore the terminal's saved output
flags around the hook call and re-enter raw mode afterwards; only oflag is
toggled so ECHO/ICANON stay off at the prompt.
The Emscripten buildbot has the pty module but no pty devices, so
pty.openpty() raises OSError("out of pty devices").  Guard the test
class the same way Lib/test/test_pty.py does.
Comment thread Lib/_pyrepl/unix_console.py Outdated
cooked.oflag = self.__svtermstate.oflag
self.__input_fd_set(cooked)
try:
posix._inputhook()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we return posix._inputhook() here? This keeps the Unix wrapper consistent with the console API and the Windows path.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

self.assertFalse(_termios.tcgetattr(slave_fd)[1] & _termios.OPOST)
finally:
console.restore()
os.close(slave_fd)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, feel free to ignore: relying on a background reader plus time.sleep(0.2) to observe the output tends to be a source of buildbot flakiness. Since the TCSADRAIN in __run_input_hook already guarantees the bytes reach the master before the hook returns, can we drain master_fd directly after hook() instead of sleeping?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried that first but it hangs on macOS — TCSADRAIN there doesn't return until the master side actually reads the output, so the mode switch blocks forever without a reader. Kept the reader thread but dropped the sleep: once the hook returns the output is already drained, so joining the reader is enough.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread Lib/_pyrepl/unix_console.py Outdated
return self.__run_input_hook

def __run_input_hook(self):
# Input-hook callbacks (GUI toolkit event loops, and any warning,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is too verbose, pls, simplify it a bit

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@unittest.skipIf(is_android or is_apple_mobile or is_wasm32,
"pty is not available on this platform")
class TestUnixConsoleInputHook(TestCase):
# gh-152907: pyrepl runs with OPOST disabled so it can drive the cursor

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thhese AI comments are too verbose pls keep only the relevant parts

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, trimmed them down.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants