Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions Lib/_pyrepl/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
from dataclasses import dataclass, field
from typing import Literal, Protocol, Self

from .utils import ANSI_ESCAPE_SEQUENCE, THEME, StyleRef, str_width
from _colorize import ANSIColors

from .types import CursorXY
from .utils import ANSI_ESCAPE_SEQUENCE, THEME, StyleRef, str_width

type RenderStyle = StyleRef | str | None
type LineUpdateKind = Literal[
Expand Down Expand Up @@ -55,7 +57,7 @@ def _style_escape(style: StyleRef) -> str:


def _update_terminal_state(state: str, escape: str) -> str:
if escape in {"\x1b[0m", "\x1b[m"}:
if escape in {ANSIColors.RESET, "\x1b[m"}:
return ""
return state + escape

Expand Down Expand Up @@ -344,14 +346,14 @@ def render_cells(
target_escape += visual_style
if target_escape != active_escape:
if active_escape:
rendered.append("\x1b[0m")
rendered.append(ANSIColors.RESET)
if target_escape:
rendered.append(target_escape)
active_escape = target_escape
rendered.append(cell.text)

if active_escape:
rendered.append("\x1b[0m")
rendered.append(ANSIColors.RESET)
return "".join(rendered)


Expand Down
4 changes: 4 additions & 0 deletions Lib/_pyrepl/unix_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
from fcntl import ioctl
from typing import TYPE_CHECKING, overload

from _colorize import ANSIColors

from . import terminfo
from .console import Console, Event
from .fancy_termios import tcgetattr, tcsetattr, TermState
Expand Down Expand Up @@ -517,6 +519,7 @@ def restore(self) -> None:
Restore the console to the default state
"""
trace("unix.restore")
self.__write(ANSIColors.RESET)
self.__disable_bracketed_paste()
self.__maybe_write_code(self._rmkx)
self.flushoutput()
Expand Down Expand Up @@ -654,6 +657,7 @@ def finish(self):
while y >= 0 and not rendered_lines[y].text:
y -= 1
self.__move(0, min(y, self.height + self.__offset - 1))
self.__write(ANSIColors.RESET)
Comment thread
edvilme marked this conversation as resolved.
self.__write("\n\r")
self.flushoutput()

Expand Down
5 changes: 5 additions & 0 deletions Lib/_pyrepl/windows_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
)
from ctypes import Structure, POINTER, Union
from typing import TYPE_CHECKING

from _colorize import ANSIColors

from .console import Event, Console
from .render import (
EMPTY_RENDER_LINE,
Expand Down Expand Up @@ -480,6 +483,7 @@ def prepare(self) -> None:
def restore(self) -> None:
trace("windows.restore")
if self.__vt_support:
self.__write(ANSIColors.RESET)
# Recover to original mode before running REPL
self._disable_bracketed_paste()
if not SetConsoleMode(InHandle, self.__original_input_mode):
Expand Down Expand Up @@ -647,6 +651,7 @@ def finish(self) -> None:
while y >= 0 and not rendered_lines[y].text:
y -= 1
self._move_relative(0, min(y, self.height + self.__offset - 1))
self.__write(ANSIColors.RESET)
self.__write("\r\n")

def flushoutput(self) -> None:
Expand Down
19 changes: 19 additions & 0 deletions Lib/test/test_pyrepl/test_unix_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import threading
import unittest
from functools import partial
from _colorize import ANSIColors
from test.support import force_color, os_helper, force_not_colorized_test_class
from test.support import threading_helper

Expand Down Expand Up @@ -147,6 +148,24 @@ def test_no_newline(self, _os_write):
self.assertNotIn(call(ANY, b'\n'), _os_write.mock_calls)
con.restore()

def test_reset_on_finish(self, _os_write):
# gh-152068: finish() must emit the ANSI reset sequence so any
# active color does not leak past the prompt.
code = "1"
events = code_to_events(code)
_, con = handle_events_unix_console(events)
con.finish()
_os_write.assert_any_call(ANY, ANSIColors.RESET.encode(con.encoding))
con.restore()

def test_reset_on_restore(self, _os_write):
# gh-152068: restore() must emit the ANSI reset sequence.
code = "1"
events = code_to_events(code)
_, con = handle_events_unix_console(events)
con.restore()
_os_write.assert_any_call(ANY, ANSIColors.RESET.encode(con.encoding))

def test_newline(self, _os_write):
code = "\n"
events = code_to_events(code)
Expand Down
23 changes: 23 additions & 0 deletions Lib/test/test_pyrepl/test_windows_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@


import itertools
from _colorize import ANSIColors
from functools import partial
from test.support import force_not_colorized_test_class
from typing import Iterable
Expand Down Expand Up @@ -380,6 +381,28 @@ def test_multiline_ctrl_z(self):
self.assertEqual(reader.cxy, (2, 3))
con.restore()

def test_reset_on_finish(self):
# gh-152068: finish() must emit the ANSI reset sequence so any
# active color does not leak past the prompt.
code = "1"
events = code_to_events(code)
_, con = self.handle_events(events)
con.finish()
con.out.write.assert_any_call(ANSIColors.RESET.encode(con.encoding))
con.restore()

def test_reset_on_restore(self):
# gh-152068: restore() must emit the ANSI reset sequence when VT
# support is enabled.
code = "1"
events = code_to_events(code)
_, con = self.handle_events(events)
con._WindowsConsole__vt_support = True
con._WindowsConsole__original_input_mode = 0
with patch.object(wc, "SetConsoleMode", return_value=1):
con.restore()
con.out.write.assert_any_call(ANSIColors.RESET.encode(con.encoding))


@patch.object(WindowsConsole, '__init__', _mock_console_init)
class WindowsConsoleGetEventTests(TestCase):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +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
Loading