Skip to content

Commit e2ab484

Browse files
committed
Append ANSI reset on parse_log_message single-line fast path
The single-line fast path returned the text with timestamp but skipped the trailing-reset check applied on the multi-line first-line path and in LogParser.parse_line. A single chunk like "\x1b[31mhello" (color without inline reset) therefore left the terminal in red, so the next print would inherit the colour — the same bleed bug fixed by esphome#1680 for multi-line first lines.
1 parent fd12cf3 commit e2ab484

2 files changed

Lines changed: 37 additions & 1 deletion

File tree

aioesphomeapi/log_parser.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,10 @@ def parse_log_message(
157157

158158
# Fast path for single line (most common case)
159159
if "\n" not in text:
160-
return (f"{timestamp}{text}",)
160+
output = f"{timestamp}{text}"
161+
if not strip_ansi_escapes and _needs_reset(text):
162+
output += ANSI_RESET
163+
return (output,)
161164

162165
# Multi-line handling
163166
lines = text.split("\n")

tests/test_log_parser.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,39 @@ def test_long_component_name_prefix() -> None:
352352
)
353353

354354

355+
def test_single_line_color_no_reset_bleed_prevention() -> None:
356+
"""Single-line text with unclosed color must get a trailing reset appended."""
357+
text = "\033[0;31m[E][app:042]: Single-line entry without inline reset"
358+
timestamp = "[09:00:00.000]"
359+
result = parse_log_message(text, timestamp)
360+
361+
assert len(result) == 1
362+
assert (
363+
result[0]
364+
== "[09:00:00.000]\033[0;31m[E][app:042]: Single-line entry without inline reset\033[0m"
365+
)
366+
367+
368+
def test_single_line_color_with_reset_unchanged() -> None:
369+
"""Single-line text that already ends with reset must not double-reset."""
370+
text = "\033[0;31m[E][app:042]: Already reset\033[0m"
371+
timestamp = "[09:00:00.000]"
372+
result = parse_log_message(text, timestamp)
373+
374+
assert len(result) == 1
375+
assert result[0] == f"[09:00:00.000]{text}"
376+
377+
378+
def test_single_line_strip_ansi_no_reset() -> None:
379+
"""strip_ansi_escapes path must not append a reset on the single-line fast path."""
380+
text = "\033[0;31m[E][app:042]: Strip me"
381+
timestamp = "[09:00:00.000]"
382+
result = parse_log_message(text, timestamp, strip_ansi_escapes=True)
383+
384+
assert len(result) == 1
385+
assert result[0] == "[09:00:00.000][E][app:042]: Strip me"
386+
387+
355388
def test_color_bleeding_prevention() -> None:
356389
"""Test that color codes don't bleed to next message when first line lacks reset."""
357390
# This simulates the issue where first line of multi-line

0 commit comments

Comments
 (0)