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
20 changes: 14 additions & 6 deletions Lib/_pyrepl/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,21 @@ def str_width(c: str) -> int:


def wlen(s: str) -> int:
if len(s) == 1 and s != "\x1a":
if len(s) == 1 and s!= "\x1a":
return str_width(s)
length = sum(str_width(i) for i in s)
# remove lengths of any escape sequences
sequence = ANSI_ESCAPE_SEQUENCE.findall(s)
ctrl_z_cnt = s.count("\x1a")
return length - sum(len(i) for i in sequence) + ctrl_z_cnt
length = 0
# Strip ANSI escapes first
plain = ANSI_ESCAPE_SEQUENCE.sub("", s)
for c in plain:
if c == "\b":
# Backspace decreases cursor position, and cannot move before column 0.
length = max(length - 1, 0)
elif c == "\x1a":
# Control-Z is treated as width 2 to account for the fact that it will be rendered as ^Z
length += 2
else:
length += str_width(c)
return length


def unbracket(s: str, including_content: bool = False) -> str:
Expand Down
6 changes: 6 additions & 0 deletions Lib/test/test_pyrepl/test_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,12 @@ def test_prompt_length(self):
self.assertEqual(prompt, "\033[0;32m樂>\033[0m> ")
self.assertEqual(l, 5)

# Handles backspace in prompt
ps1 = "Question? _\b"
prompt, l = Reader.process_prompt(ps1)
self.assertEqual(prompt, ps1)
self.assertEqual(l, 10)

def test_prepare_with_zero_width_does_not_crash(self):
console = prepare_console([], width=0)
reader = ReadlineAlikeReader(console=console, config=ReadlineConfig())
Expand Down
7 changes: 7 additions & 0 deletions Lib/test/test_pyrepl/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ def test_wlen(self):
self.assertEqual(wlen('e\N{COMBINING ACUTE ACCENT}'), 1)
self.assertEqual(wlen('a\N{ZERO WIDTH JOINER}b'), 2)

# Backspace decreases cursor position, and cannot move before column 0.
self.assertEqual(wlen('Question? _\b'), 10)
self.assertEqual(wlen('ab\b'), 1)
self.assertEqual(wlen('ab\b\b'), 0)
self.assertEqual(wlen('\babc'), 3)
self.assertEqual(wlen('樂\b'), 1)

def test_prev_next_window(self):
def gen_normal():
yield 1
Expand Down
Loading