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:
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)
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.
@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)
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)