Skip to content

Commit 6517434

Browse files
committed
Add textwrap.wrap new ignore_ansi_escape parameter, to ignore ANSI escape codes
1 parent 5ec03cf commit 6517434

File tree

2 files changed

+20
-4
lines changed

2 files changed

+20
-4
lines changed

Lib/argparse.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -691,14 +691,15 @@ def _split_lines(self, text, width):
691691
# The textwrap module is used only for formatting help.
692692
# Delay its import for speeding up the common usage of argparse.
693693
import textwrap
694-
return textwrap.wrap(text, width)
694+
return textwrap.wrap(text, width, ignore_ansi_escape=True)
695695

696696
def _fill_text(self, text, width, indent):
697697
text = self._whitespace_matcher.sub(' ', text).strip()
698698
import textwrap
699699
return textwrap.fill(text, width,
700700
initial_indent=indent,
701-
subsequent_indent=indent)
701+
subsequent_indent=indent,
702+
ignore_ansi_escape=True)
702703

703704
def _get_help_string(self, action):
704705
return action.help

Lib/textwrap.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ class TextWrapper:
6161
Truncate wrapped lines.
6262
placeholder (default: ' [...]')
6363
Append to the last line of truncated text.
64+
ignore_ansi_escape (default: false)
65+
Ignore ANSI escape sequences when computing lengths of lines.
6466
"""
6567

6668
unicode_whitespace_trans = dict.fromkeys(map(ord, _whitespace), ord(' '))
@@ -109,6 +111,8 @@ class TextWrapper:
109111
r'[\"\']?' # optional end-of-quote
110112
r'\z') # end of chunk
111113

114+
ansi_escape_re = re.compile(r'\x1b\[[0-9;]*m')
115+
112116
def __init__(self,
113117
width=70,
114118
initial_indent="",
@@ -122,7 +126,8 @@ def __init__(self,
122126
tabsize=8,
123127
*,
124128
max_lines=None,
125-
placeholder=' [...]'):
129+
placeholder=' [...]',
130+
ignore_ansi_escape=False):
126131
self.width = width
127132
self.initial_indent = initial_indent
128133
self.subsequent_indent = subsequent_indent
@@ -135,6 +140,7 @@ def __init__(self,
135140
self.tabsize = tabsize
136141
self.max_lines = max_lines
137142
self.placeholder = placeholder
143+
self.ignore_ansi_escape = ignore_ansi_escape
138144

139145

140146
# -- Private methods -----------------------------------------------
@@ -235,6 +241,10 @@ def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
235241
# cur_len will be zero, so the next line will be entirely
236242
# devoted to the long word that we can't handle right now.
237243

244+
def _str_len_without_ansi_escape_codes(self, s):
245+
"""Return the length of string s without ANSI escape codes."""
246+
return len(self.ansi_escape_re.sub(s, ""))
247+
238248
def _wrap_chunks(self, chunks):
239249
"""_wrap_chunks(chunks : [string]) -> [string]
240250
@@ -259,6 +269,11 @@ def _wrap_chunks(self, chunks):
259269
if len(indent) + len(self.placeholder.lstrip()) > self.width:
260270
raise ValueError("placeholder too large for max width")
261271

272+
if self.ignore_ansi_escape:
273+
_str_len = self._str_len_without_ansi_escape_codes
274+
else:
275+
_str_len = len
276+
262277
# Arrange in reverse order so items can be efficiently popped
263278
# from a stack of chucks.
264279
chunks.reverse()
@@ -285,7 +300,7 @@ def _wrap_chunks(self, chunks):
285300
del chunks[-1]
286301

287302
while chunks:
288-
l = len(chunks[-1])
303+
l = _str_len(chunks[-1])
289304

290305
# Can at least squeeze this chunk onto the current line.
291306
if cur_len + l <= width:

0 commit comments

Comments
 (0)