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
5 changes: 5 additions & 0 deletions Doc/library/textwrap.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ functions should be good enough; otherwise, you should use an instance of
>>> textwrap.shorten("Hello world", width=10, placeholder="...")
'Hello...'

If no words can be kept before the *placeholder* (for example, when
*text* is a single word longer than *width*) and *break_long_words* is
true, the word is broken so that part of it is kept before the
*placeholder*.

Optional keyword arguments correspond to the instance attributes of
:class:`TextWrapper`, documented below. Note that the whitespace is
collapsed before the text is passed to the :class:`TextWrapper` :meth:`fill`
Expand Down
31 changes: 30 additions & 1 deletion Lib/test/test_textwrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -636,9 +636,28 @@ def test_max_lines_long(self):
['Did you say',
'"supercalifr',
'agilisticexp',
'[...]'],
'ialido [...]'],
max_lines=4)

def test_max_lines_long_word_broken(self):
# A long word broken across lines is broken further on the last
# line to make room for the placeholder, instead of dropping the
# whole remaining piece.
self.check_wrap('x' * 30, 10,
['xxxxxxxxxx', 'xxxx [...]'],
max_lines=2)
self.check_wrap('x' * 30, 10,
['xxxx [...]'],
max_lines=1)
# Not enough room for the placeholder plus any text: the
# placeholder-only line is kept.
self.check_wrap('x' * 30, 5,
['xxxxx', '[...]'],
max_lines=2)
self.check_wrap('x' * 30, 10,
['xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'],
max_lines=2, break_long_words=False)


class LongWordWithHyphensTestCase(BaseTestCase):
def setUp(self):
Expand Down Expand Up @@ -1132,6 +1151,16 @@ def test_width_too_small_for_placeholder(self):
def test_first_word_too_long_but_placeholder_fits(self):
self.check_shorten("Helloo", 5, "[...]")

def test_long_word_broken_to_fit_placeholder(self):
# A single word longer than the width is broken further to make
# room for the placeholder, instead of being replaced by it alone.
self.check_shorten("x" * 30, 10, "xxxx [...]")
self.check_shorten("x" * 30, 20, "xxxxxxxxxxxxxx [...]")
# Not enough room for the placeholder plus any text.
self.check_shorten("x" * 30, 5, "[...]")
# Words are only broken when break_long_words is true.
self.check_shorten("x" * 30, 10, "[...]", break_long_words=False)


if __name__ == '__main__':
unittest.main()
17 changes: 17 additions & 0 deletions Lib/textwrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,23 @@ def _wrap_chunks(self, chunks):
cur_line.append(self.placeholder)
lines.append(indent + ''.join(cur_line))
break
if (self.break_long_words and
len(cur_line) == 1 and
cur_line[-1].strip() and
len(cur_line[-1]) == width and
width - len(self.placeholder) > 0):
# The placeholder does not fit after this line,
# and the line consists of a single full-width
# chunk: a piece of a long word broken by
# _handle_long_word(), or a word exactly as wide
# as the line. Break it further instead of
# dropping it whole, or the last line (and, for
# shorten(), the whole text) would be replaced
# by the placeholder alone.
space_left = width - len(self.placeholder)
cur_line[-1] = cur_line[-1][:space_left]
cur_len = space_left
continue
cur_len -= len(cur_line[-1])
del cur_line[-1]
else:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix :func:`textwrap.shorten` and :func:`textwrap.wrap` with ``max_lines``
discarding a broken long word -- and with it, for :func:`textwrap.shorten`,
all remaining text -- instead of breaking it further to make room for the
placeholder.
Loading