Skip to content

Commit 2037345

Browse files
fix(terminal): remove color modifier for the VT100 Erase in Line (#614)
1 parent 0dbab3c commit 2037345

4 files changed

Lines changed: 166 additions & 3 deletions

File tree

libs/pyTermTk/TermTk/TTkCore/color.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,9 @@ def colorType(self) -> int:
335335
( TTkK.ColorType.Foreground if self._fg else TTkK.NONE ) |
336336
( TTkK.ColorType.Background if self._bg else TTkK.NONE ) )
337337

338+
def withoutModifiers(self) -> TTkColor:
339+
return self
340+
338341
@staticmethod
339342
def rgb2hsl(rgb) -> Tuple[int,int,int]:
340343
r = rgb[0]/255
@@ -534,6 +537,9 @@ def colorType(self) -> int:
534537
super().colorType() |
535538
( TTkK.ColorType.Modifier if self._mod else TTkK.NONE ))
536539

540+
def withoutModifiers(self) -> TTkColor:
541+
return TTkColor(fg=self._fg, bg=self._bg)
542+
537543
def __str__(self) -> str:
538544
if not self._buffer:
539545
self._buffer = TTkTermColor.rgb2ansi(

libs/pyTermTk/TermTk/TTkWidgets/TTkTerminal/terminal_screen_CSI.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,11 @@ def _CSI_K_EL(self, ps, _):
133133
x,y = self._terminalCursor
134134
w,h = self._w,self._h
135135
if ps == 0:
136-
self._canvas.fill(char=' ', pos=(x,y),size=(w-x,1), color=self._color)
136+
self._canvas.fill(char=' ', pos=(x,y),size=(w-x,1), color=self._color.withoutModifiers())
137137
elif ps == 1:
138-
self._canvas.fill(char=' ', pos=(0,y),size=(x,1), color=self._color)
138+
self._canvas.fill(char=' ', pos=(0,y),size=(x,1), color=self._color.withoutModifiers())
139139
elif ps == 2:
140-
self._canvas.fill(char=' ', pos=(0,y),size=(w,1), color=self._color)
140+
self._canvas.fill(char=' ', pos=(0,y),size=(w,1), color=self._color.withoutModifiers())
141141

142142
# CSI ? Ps K
143143
# Erase in Line (DECSEL), VT220.

tests/pytest/test_007_terminal.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,36 @@ def test_erase_entire_line(self):
298298
self.screen._CSI_K_EL(2, None)
299299
assert _get_screen_text(self.screen, 0) == ""
300300

301+
def test_erase_to_right_preserves_fg_bg_but_removes_modifiers(self):
302+
self.screen._terminalCursor = (0, 0)
303+
self.screen._pushTxt("ABCDEFGHIJ")
304+
305+
styled = TTkColor.fg('#112233') + TTkColor.bg('#445566') + TTkColor.UNDERLINE + TTkColor.BOLD
306+
self.screen.setColor(styled)
307+
self.screen._terminalCursor = (5, 0)
308+
self.screen._CSI_K_EL(0, None)
309+
310+
erased_color = self.screen._canvas._colors[0][5]
311+
assert erased_color.fgToRGB() == (0x11, 0x22, 0x33)
312+
assert erased_color.bgToRGB() == (0x44, 0x55, 0x66)
313+
assert not erased_color.underline()
314+
assert not erased_color.bold()
315+
316+
def test_erase_to_left_preserves_fg_bg_but_removes_modifiers(self):
317+
self.screen._terminalCursor = (0, 0)
318+
self.screen._pushTxt("ABCDEFGHIJ")
319+
320+
styled = TTkColor.fg('#aabbcc') + TTkColor.bg('#010203') + TTkColor.UNDERLINE + TTkColor.ITALIC
321+
self.screen.setColor(styled)
322+
self.screen._terminalCursor = (5, 0)
323+
self.screen._CSI_K_EL(1, None)
324+
325+
erased_color = self.screen._canvas._colors[0][0]
326+
assert erased_color.fgToRGB() == (0xaa, 0xbb, 0xcc)
327+
assert erased_color.bgToRGB() == (0x01, 0x02, 0x03)
328+
assert not erased_color.underline()
329+
assert not erased_color.italic()
330+
301331

302332
class TestTerminalCSICharManipulation:
303333
'''Tests for CSI character insertion/deletion escape sequences.'''

tests/test.ANSI.underline.sh

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
# ANSI SGR helpers
6+
UL=$'\e[4m' # underline on
7+
NO_UL=$'\e[24m' # underline off
8+
RESET=$'\e[0m' # reset all attributes
9+
BOLD=$'\e[1m'
10+
RED=$'\e[31m'
11+
GREEN=$'\e[32m'
12+
YELLOW=$'\e[33m'
13+
CYAN=$'\e[36m'
14+
EL0=$'\e[K' # erase from cursor to end of line
15+
16+
move_to_col() {
17+
printf '\r\e[%dG' "$1"
18+
}
19+
20+
hr() {
21+
printf '%s\n' '------------------------------------------------------------'
22+
}
23+
24+
section() {
25+
printf '%b\n' "${BOLD}${CYAN}$1${RESET}"
26+
}
27+
28+
pass_hint() {
29+
printf '%b\n' "${GREEN}Expected:${RESET} $1"
30+
}
31+
32+
fail_hint() {
33+
printf '%b\n' "${YELLOW}Watch for:${RESET} $1"
34+
}
35+
36+
printf '%b\n' "${BOLD}Terminal Underline Feature Test${RESET}"
37+
printf 'Shell: %s\n' "${SHELL:-unknown}"
38+
printf 'TERM : %s\n' "${TERM:-unknown}"
39+
hr
40+
41+
section '1) Basic underline on/off'
42+
printf 'Normal -> %bunderlined%b -> normal\n' "${UL}" "${NO_UL}"
43+
pass_hint 'Only the word "underlined" is underlined.'
44+
fail_hint 'Underline leaking into text after NO_UL.'
45+
hr
46+
47+
section '2) Reset behavior (SGR 0)'
48+
printf '%bUNDERLINED then RESET%b then plain text\n' "${UL}" "${RESET}"
49+
pass_hint 'Text after RESET is not underlined.'
50+
fail_hint 'Attributes persisting after RESET.'
51+
hr
52+
53+
section '3) Mixed attributes'
54+
printf '%b%bBold+Underline%b still bold only%b plain\n' "${BOLD}" "${UL}" "${NO_UL}" "${RESET}"
55+
pass_hint 'Underline ends at NO_UL while bold remains until RESET.'
56+
fail_hint 'NO_UL disabling unrelated attributes (like bold).'
57+
hr
58+
59+
section '4) Color + underline'
60+
printf '%b%bRed underlined text%b and red not-underlined%b plain\n' "${RED}" "${UL}" "${NO_UL}" "${RESET}"
61+
pass_hint 'Color continues after NO_UL; underline does not.'
62+
fail_hint 'Underline reset clearing color unexpectedly.'
63+
hr
64+
65+
section '5) Wrap test (bleed detection)'
66+
printf 'This line is designed to be long enough to wrap in a narrow terminal window. '
67+
printf '%bUNDERLINE_START%b ' "${UL}" "${NO_UL}"
68+
printf 'If your emulator has a bug, underline may continue on the next visual line.\n'
69+
pass_hint 'Only UNDERLINE_START is underlined, even when wrapped.'
70+
fail_hint 'Underline bleeding into next wrapped segment or next line.'
71+
hr
72+
73+
section '5b) Target repro: underline + erase-to-EOL'
74+
printf 'Case A (intentional-bad order): '
75+
printf '%bUNDERLINED TITLE' "${UL}"
76+
printf '%b' "${EL0}"
77+
printf '%b\n' "${NO_UL}"
78+
pass_hint 'If your terminal applies attributes to erased cells, the rest of this line will be underlined.'
79+
fail_hint 'No underline to EOL here means your emulator likely does not reproduce the bug.'
80+
81+
printf 'Case B (safe order): '
82+
printf '%bUNDERLINED TITLE%b' "${UL}" "${NO_UL}"
83+
printf '%b\n' "${EL0}"
84+
pass_hint 'Underline should stop at TITLE, with no underline to line end.'
85+
fail_hint 'Underline to EOL here indicates a stronger attribute-state bug.'
86+
hr
87+
88+
section '5c) Target repro: status-line style redraw'
89+
printf 'A tool that redraws a line with CR + clear can accidentally keep underline active.\n'
90+
printf 'Watch the first row while it updates...\n'
91+
for i in {1..8}; do
92+
printf '\r'
93+
printf 'Status: '
94+
if (( i % 2 == 0 )); then
95+
printf '%bRUNNING%b ' "${UL}" "${NO_UL}"
96+
printf '%bRUNNING%b' "${UL}" "${NO_UL}"
97+
else
98+
printf 'RUNNING ' "${UL}" "${NO_UL}"
99+
printf '%bRUNNING' "${UL}"
100+
printf '%b' "${EL0}"
101+
printf '%b' "${NO_UL}"
102+
fi
103+
printf ' tick=%d ' "$i"
104+
sleep 0.50
105+
done
106+
printf '\n'
107+
pass_hint 'Even ticks usually look fine; odd ticks may show underline to EOL on affected emulators.'
108+
fail_hint 'Unexpected trailing underline after redraw operations.'
109+
hr
110+
111+
section '6) Newline boundary'
112+
printf '%bLine A underlined%b\n' "${UL}" "${NO_UL}"
113+
printf 'Line B should be plain\n'
114+
pass_hint 'Line B is not underlined.'
115+
fail_hint 'Underline carrying over across newline.'
116+
hr
117+
118+
section '7) Stress pattern'
119+
for i in {1..10}; do
120+
printf 'Row %02d: normal | %bUL%b | normal | %bUL%b | normal\n' "$i" "${UL}" "${NO_UL}" "${UL}" "${NO_UL}"
121+
done
122+
pass_hint 'Underline appears only inside UL markers on every row.'
123+
fail_hint 'Random attribute drift after repeated toggles.'
124+
hr
125+
126+
printf '%b\n' "${BOLD}Done.${RESET}"
127+
printf 'Tip: Resize terminal narrower and re-run to test wrapping edge cases.\n'

0 commit comments

Comments
 (0)