mirror of
https://github.com/python/cpython.git
synced 2026-01-06 23:42:34 +00:00
[3.14] gh-128067: Fix pyrepl overriding printed output without newlines (GH-138732) (GH-143350)
(cherry picked from commit 8a2deea1fc)
Co-authored-by: Jan-Eric Nitschke <47750513+JanEricNitschke@users.noreply.github.com>
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
This commit is contained in:
parent
dc16c7e9a9
commit
a141b840af
7 changed files with 101 additions and 6 deletions
|
|
@ -251,8 +251,9 @@ def refresh(self, screen, c_xy):
|
|||
if not self.__gone_tall:
|
||||
while len(self.screen) < min(len(screen), self.height):
|
||||
self.__hide_cursor()
|
||||
self.__move(0, len(self.screen) - 1)
|
||||
self.__write("\n")
|
||||
if self.screen:
|
||||
self.__move(0, len(self.screen) - 1)
|
||||
self.__write("\n")
|
||||
self.posxy = 0, len(self.screen)
|
||||
self.screen.append("")
|
||||
else:
|
||||
|
|
@ -808,7 +809,7 @@ def __tputs(self, fmt, prog=delayprog):
|
|||
will never do anyone any good."""
|
||||
# using .get() means that things will blow up
|
||||
# only if the bps is actually needed (which I'm
|
||||
# betting is pretty unlkely)
|
||||
# betting is pretty unlikely)
|
||||
bps = ratedict.get(self.__svtermstate.ospeed)
|
||||
while True:
|
||||
m = prog.search(fmt)
|
||||
|
|
|
|||
|
|
@ -183,8 +183,9 @@ def refresh(self, screen: list[str], c_xy: tuple[int, int]) -> None:
|
|||
|
||||
while len(self.screen) < min(len(screen), self.height):
|
||||
self._hide_cursor()
|
||||
self._move_relative(0, len(self.screen) - 1)
|
||||
self.__write("\n")
|
||||
if self.screen:
|
||||
self._move_relative(0, len(self.screen) - 1)
|
||||
self.__write("\n")
|
||||
self.posxy = 0, len(self.screen)
|
||||
self.screen.append("")
|
||||
|
||||
|
|
@ -514,7 +515,7 @@ def clear(self) -> None:
|
|||
"""Wipe the screen"""
|
||||
self.__write(CLEAR)
|
||||
self.posxy = 0, 0
|
||||
self.screen = [""]
|
||||
self.screen = []
|
||||
|
||||
def finish(self) -> None:
|
||||
"""Move the cursor to the end of the display and otherwise get
|
||||
|
|
|
|||
|
|
@ -1836,6 +1836,69 @@ def test_showrefcount(self):
|
|||
self.assertEqual(len(matches), 3)
|
||||
|
||||
|
||||
@force_not_colorized
|
||||
def test_no_newline(self):
|
||||
env = os.environ.copy()
|
||||
env.pop("PYTHON_BASIC_REPL", "")
|
||||
env["PYTHON_BASIC_REPL"] = "1"
|
||||
|
||||
commands = "print('Something pretty long', end='')\nexit()\n"
|
||||
expected_output_sequence = "Something pretty long>>> exit()"
|
||||
|
||||
basic_output, basic_exit_code = self.run_repl(commands, env=env)
|
||||
self.assertEqual(basic_exit_code, 0)
|
||||
self.assertIn(expected_output_sequence, basic_output)
|
||||
|
||||
output, exit_code = self.run_repl(commands)
|
||||
self.assertEqual(exit_code, 0)
|
||||
|
||||
# Build patterns for escape sequences that don't affect cursor position
|
||||
# or visual output. Use terminfo to get platform-specific sequences,
|
||||
# falling back to hard-coded patterns for capabilities not in terminfo.
|
||||
from _pyrepl.terminfo import TermInfo
|
||||
ti = TermInfo(os.environ.get("TERM", ""))
|
||||
|
||||
safe_patterns = []
|
||||
|
||||
# smkx/rmkx - application cursor keys and keypad mode
|
||||
smkx = ti.get("smkx")
|
||||
rmkx = ti.get("rmkx")
|
||||
if smkx:
|
||||
safe_patterns.append(re.escape(smkx.decode("ascii")))
|
||||
if rmkx:
|
||||
safe_patterns.append(re.escape(rmkx.decode("ascii")))
|
||||
if not smkx and not rmkx:
|
||||
safe_patterns.append(r'\x1b\[\?1[hl]') # application cursor keys
|
||||
safe_patterns.append(r'\x1b[=>]') # application keypad mode
|
||||
|
||||
# ich1 - insert character (only safe form that inserts exactly 1 char)
|
||||
ich1 = ti.get("ich1")
|
||||
if ich1:
|
||||
safe_patterns.append(re.escape(ich1.decode("ascii")) + r'(?=[ -~])')
|
||||
else:
|
||||
safe_patterns.append(r'\x1b\[(?:1)?@(?=[ -~])')
|
||||
|
||||
# civis/cnorm - cursor visibility (may include cursor blinking control)
|
||||
civis = ti.get("civis")
|
||||
cnorm = ti.get("cnorm")
|
||||
if civis:
|
||||
safe_patterns.append(re.escape(civis.decode("ascii")))
|
||||
if cnorm:
|
||||
safe_patterns.append(re.escape(cnorm.decode("ascii")))
|
||||
if not civis and not cnorm:
|
||||
safe_patterns.append(r'\x1b\[\?25[hl]') # cursor visibility
|
||||
safe_patterns.append(r'\x1b\[\?12[hl]') # cursor blinking
|
||||
|
||||
# Modern extensions not in standard terminfo - always use patterns
|
||||
safe_patterns.append(r'\x1b\[\?2004[hl]') # bracketed paste mode
|
||||
safe_patterns.append(r'\x1b\[\?12[hl]') # cursor blinking (may be separate)
|
||||
safe_patterns.append(r'\x1b\[\?[01]c') # device attributes
|
||||
|
||||
safe_escapes = re.compile('|'.join(safe_patterns))
|
||||
cleaned_output = safe_escapes.sub('', output)
|
||||
self.assertIn(expected_output_sequence, cleaned_output)
|
||||
|
||||
|
||||
class TestPyReplCtrlD(TestCase):
|
||||
"""Test Ctrl+D behavior in _pyrepl to match old pre-3.13 REPL behavior.
|
||||
|
||||
|
|
|
|||
|
|
@ -102,6 +102,20 @@ def unix_console(events, **kwargs):
|
|||
@patch("os.write")
|
||||
@force_not_colorized_test_class
|
||||
class TestConsole(TestCase):
|
||||
def test_no_newline(self, _os_write):
|
||||
code = "1"
|
||||
events = code_to_events(code)
|
||||
_, con = handle_events_unix_console(events)
|
||||
self.assertNotIn(call(ANY, b'\n'), _os_write.mock_calls)
|
||||
con.restore()
|
||||
|
||||
def test_newline(self, _os_write):
|
||||
code = "\n"
|
||||
events = code_to_events(code)
|
||||
_, con = handle_events_unix_console(events)
|
||||
_os_write.assert_any_call(ANY, b"\n")
|
||||
con.restore()
|
||||
|
||||
def test_simple_addition(self, _os_write):
|
||||
code = "12+34"
|
||||
events = code_to_events(code)
|
||||
|
|
|
|||
|
|
@ -72,6 +72,20 @@ def handle_events_short(self, events, **kwargs):
|
|||
def handle_events_height_3(self, events):
|
||||
return self.handle_events(events, height=3)
|
||||
|
||||
def test_no_newline(self):
|
||||
code = "1"
|
||||
events = code_to_events(code)
|
||||
_, con = self.handle_events(events)
|
||||
self.assertNotIn(call(b'\n'), con.out.write.mock_calls)
|
||||
con.restore()
|
||||
|
||||
def test_newline(self):
|
||||
code = "\n"
|
||||
events = code_to_events(code)
|
||||
_, con = self.handle_events(events)
|
||||
con.out.write.assert_any_call(b"\n")
|
||||
con.restore()
|
||||
|
||||
def test_simple_addition(self):
|
||||
code = "12+34"
|
||||
events = code_to_events(code)
|
||||
|
|
|
|||
|
|
@ -1344,6 +1344,7 @@ Gustavo Niemeyer
|
|||
Oscar Nierstrasz
|
||||
Lysandros Nikolaou
|
||||
Hrvoje Nikšić
|
||||
Jan-Eric Nitschke
|
||||
Gregory Nofi
|
||||
Jesse Noller
|
||||
Bill Noon
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Fix a bug in PyREPL on Windows where output without a trailing newline was overwritten by the next prompt.
|
||||
Loading…
Add table
Add a link
Reference in a new issue