Skip to content

Commit bc7cc24

Browse files
authored
[3.14] gh-152068: Reset PyREPL Colors on prompt finish (GH-152108) (#154402)
(cherry picked from commit 832d557)
1 parent 5eaf930 commit bc7cc24

5 files changed

Lines changed: 54 additions & 1 deletion

File tree

‎Lib/_pyrepl/unix_console.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
import platform
3434
from fcntl import ioctl
3535

36+
from _colorize import ANSIColors
37+
3638
from . import terminfo
3739
from .console import Console, Event
3840
from .fancy_termios import tcgetattr, tcsetattr, TermState
@@ -382,6 +384,8 @@ def restore(self):
382384
"""
383385
Restore the console to the default state
384386
"""
387+
trace("unix.restore")
388+
self.__write(ANSIColors.RESET)
385389
self.__disable_bracketed_paste()
386390
self.__maybe_write_code(self._rmkx)
387391
self.flushoutput()
@@ -518,6 +522,7 @@ def finish(self):
518522
while y >= 0 and not self.screen[y]:
519523
y -= 1
520524
self.__move(0, min(y, self.height + self.__offset - 1))
525+
self.__write(ANSIColors.RESET)
521526
self.__write("\n\r")
522527
self.flushoutput()
523528

‎Lib/_pyrepl/windows_console.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@
3737
SHORT,
3838
)
3939
from ctypes import Structure, POINTER, Union
40+
41+
from _colorize import ANSIColors
42+
4043
from .console import Event, Console
4144
from .trace import trace
4245
from .utils import wlen
@@ -363,6 +366,7 @@ def prepare(self) -> None:
363366

364367
def restore(self) -> None:
365368
if self.__vt_support:
369+
self.__write(ANSIColors.RESET)
366370
# Recover to original mode before running REPL
367371
self._disable_bracketed_paste()
368372
SetConsoleMode(InHandle, self.__original_input_mode)
@@ -519,6 +523,7 @@ def finish(self) -> None:
519523
while y >= 0 and not self.screen[y]:
520524
y -= 1
521525
self._move_relative(0, min(y, self.height + self.__offset - 1))
526+
self.__write(ANSIColors.RESET)
522527
self.__write("\r\n")
523528

524529
def flushoutput(self) -> None:

‎Lib/test/test_pyrepl/test_unix_console.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import threading
77
import unittest
88
from functools import partial
9+
from _colorize import ANSIColors
910
from test.support import os_helper, force_not_colorized_test_class
1011
from test.support import threading_helper
1112

@@ -107,6 +108,24 @@ def test_no_newline(self, _os_write):
107108
self.assertNotIn(call(ANY, b'\n'), _os_write.mock_calls)
108109
con.restore()
109110

111+
def test_reset_on_finish(self, _os_write):
112+
# gh-152068: finish() must emit the ANSI reset sequence so any
113+
# active color does not leak past the prompt.
114+
code = "1"
115+
events = code_to_events(code)
116+
_, con = handle_events_unix_console(events)
117+
con.finish()
118+
_os_write.assert_any_call(ANY, ANSIColors.RESET.encode(con.encoding))
119+
con.restore()
120+
121+
def test_reset_on_restore(self, _os_write):
122+
# gh-152068: restore() must emit the ANSI reset sequence.
123+
code = "1"
124+
events = code_to_events(code)
125+
_, con = handle_events_unix_console(events)
126+
con.restore()
127+
_os_write.assert_any_call(ANY, ANSIColors.RESET.encode(con.encoding))
128+
110129
def test_newline(self, _os_write):
111130
code = "\n"
112131
events = code_to_events(code)

‎Lib/test/test_pyrepl/test_windows_console.py‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66

77

88
import itertools
9+
from _colorize import ANSIColors
910
from functools import partial
1011
from test.support import force_not_colorized_test_class
1112
from typing import Iterable
1213
from unittest import TestCase
13-
from unittest.mock import MagicMock, call
14+
from unittest.mock import MagicMock, call, patch
1415

1516
from .support import handle_all_events, code_to_events
1617
from .support import prepare_reader as default_prepare_reader
@@ -366,6 +367,28 @@ def test_multiline_ctrl_z(self):
366367
self.assertEqual(reader.cxy, (2, 3))
367368
con.restore()
368369

370+
def test_reset_on_finish(self):
371+
# gh-152068: finish() must emit the ANSI reset sequence so any
372+
# active color does not leak past the prompt.
373+
code = "1"
374+
events = code_to_events(code)
375+
_, con = self.handle_events(events)
376+
con.finish()
377+
con.out.write.assert_any_call(ANSIColors.RESET.encode(con.encoding))
378+
con.restore()
379+
380+
def test_reset_on_restore(self):
381+
# gh-152068: restore() must emit the ANSI reset sequence when VT
382+
# support is enabled.
383+
code = "1"
384+
events = code_to_events(code)
385+
_, con = self.handle_events(events)
386+
con._WindowsConsole__vt_support = True
387+
con._WindowsConsole__original_input_mode = 0
388+
with patch.object(wc, "SetConsoleMode", return_value=1):
389+
con.restore()
390+
con.out.write.assert_any_call(ANSIColors.RESET.encode(con.encoding))
391+
369392

370393
class WindowsConsoleGetEventTests(TestCase):
371394
# Virtual-Key Codes: https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixes a bug when a line was split (particularly on macOS Terminal.app) in the middle of a colorized keyword, causing the ANSI Color Reset sequence (ESC0m) to not be properly printed, causing the output to be colored when it shouldn't

0 commit comments

Comments
 (0)