@@ -1409,6 +1409,72 @@ def test_showrefcount(self):
14091409 self .assertEqual (len (matches ), 3 )
14101410
14111411
1412+ @force_not_colorized
1413+ def test_no_newline (self ):
1414+ env = os .environ .copy ()
1415+ env .pop ("PYTHON_BASIC_REPL" , "" )
1416+ env ["PYTHON_BASIC_REPL" ] = "1"
1417+
1418+ commands = "print('Something pretty long', end='')\n exit()\n "
1419+ expected_output_sequence = "Something pretty long>>> exit()"
1420+
1421+ basic_output , basic_exit_code = self .run_repl (commands , env = env )
1422+ self .assertEqual (basic_exit_code , 0 )
1423+ self .assertIn (expected_output_sequence , basic_output )
1424+
1425+ output , exit_code = self .run_repl (commands )
1426+ self .assertEqual (exit_code , 0 )
1427+
1428+ # Build patterns for escape sequences that don't affect cursor position
1429+ # or visual output. Use terminfo to get platform-specific sequences,
1430+ # falling back to hard-coded patterns for capabilities not in terminfo.
1431+ try :
1432+ from _pyrepl import curses
1433+ except ImportError :
1434+ self .skipTest ("curses required for capability discovery" )
1435+
1436+ curses .setupterm (os .environ .get ("TERM" , "" ), 1 )
1437+ safe_patterns = []
1438+
1439+ # smkx/rmkx - application cursor keys and keypad mode
1440+ smkx = curses .tigetstr ("smkx" )
1441+ rmkx = curses .tigetstr ("rmkx" )
1442+ if smkx :
1443+ safe_patterns .append (re .escape (smkx .decode ("ascii" )))
1444+ if rmkx :
1445+ safe_patterns .append (re .escape (rmkx .decode ("ascii" )))
1446+ if not smkx and not rmkx :
1447+ safe_patterns .append (r'\x1b\[\?1[hl]' ) # application cursor keys
1448+ safe_patterns .append (r'\x1b[=>]' ) # application keypad mode
1449+
1450+ # ich1 - insert character (only safe form that inserts exactly 1 char)
1451+ ich1 = curses .tigetstr ("ich1" )
1452+ if ich1 :
1453+ safe_patterns .append (re .escape (ich1 .decode ("ascii" )) + r'(?=[ -~])' )
1454+ else :
1455+ safe_patterns .append (r'\x1b\[(?:1)?@(?=[ -~])' )
1456+
1457+ # civis/cnorm - cursor visibility (may include cursor blinking control)
1458+ civis = curses .tigetstr ("civis" )
1459+ cnorm = curses .tigetstr ("cnorm" )
1460+ if civis :
1461+ safe_patterns .append (re .escape (civis .decode ("ascii" )))
1462+ if cnorm :
1463+ safe_patterns .append (re .escape (cnorm .decode ("ascii" )))
1464+ if not civis and not cnorm :
1465+ safe_patterns .append (r'\x1b\[\?25[hl]' ) # cursor visibility
1466+ safe_patterns .append (r'\x1b\[\?12[hl]' ) # cursor blinking
1467+
1468+ # Modern extensions not in standard terminfo - always use patterns
1469+ safe_patterns .append (r'\x1b\[\?2004[hl]' ) # bracketed paste mode
1470+ safe_patterns .append (r'\x1b\[\?12[hl]' ) # cursor blinking (may be separate)
1471+ safe_patterns .append (r'\x1b\[\?[01]c' ) # device attributes
1472+
1473+ safe_escapes = re .compile ('|' .join (safe_patterns ))
1474+ cleaned_output = safe_escapes .sub ('' , output )
1475+ self .assertIn (expected_output_sequence , cleaned_output )
1476+
1477+
14121478class TestPyReplCtrlD (TestCase ):
14131479 """Test Ctrl+D behavior in _pyrepl to match old pre-3.13 REPL behavior.
14141480
0 commit comments