# ignoring color escape sequences
rel_pos = 0
- # the absolute position of the most recent whitespace character
- last_whitespace = 0
+ # the absolute and relative positions of the most recent whitespace
+ # character
+ last_abs_whitespace = 0
+ last_rel_whitespace = 0
# whether the current character is part of a color escape sequence
escape = False
# the current character is the escape character
if each_character == "\x1b" and not escape:
escape = True
+ rel_pos -= 1
# the current character is within an escape sequence
elif escape:
-
- # the current character is m, which terminates the
- # escape sequence
+ rel_pos -= 1
if each_character == "m":
+ # the current character is m, which terminates the
+ # escape sequence
escape = False
- # the current character is a newline, so reset the relative
- # position (start a new line)
- elif each_character == "\n":
- rel_pos = 0
- last_whitespace = abs_pos
-
- # the current character meets the requested maximum line width,
- # so we need to backtrack and find a space at which to wrap;
- # special care is taken to avoid an off-by-one in case the
- # current character is a double-width glyph
- elif each_character != "\r" and (
- rel_pos >= width or (
- rel_pos >= width - 1 and glyph_columns(
- each_character
- ) == 2
- )
- ):
-
- # it's always possible we landed on whitespace
- if unicodedata.category(each_character) in ("Cc", "Zs"):
- last_whitespace = abs_pos
-
- # insert an eol in place of the space
- text = text[:last_whitespace] + "\r\n" + text[last_whitespace + 1:]
+ # track the most recent whitespace we've seen
+ # TODO(fungi) exclude non-breaking spaces (\x0a)
+ elif unicodedata.category(each_character) in ("Cc", "Zs"):
+ if each_character == "\n":
+ # the current character is a newline, so reset the relative
+ # position too (start a new line)
+ rel_pos = 0
+ if each_character != "\r":
+ # the current character is not a carriage return, so mark it as
+ # whitespace (we don't want to break and wrap between CR+LF)
+ last_abs_whitespace = abs_pos
+ last_rel_whitespace = rel_pos
+
+ # the current character meets the requested maximum line width, so we
+ # need to wrap unless the current word is wider than the terminal (in
+ # which case we let it do the wrapping instead)
+ if last_rel_whitespace != 0 and (rel_pos > width or (
+ rel_pos > width - 1 and glyph_columns(each_character) == 2)):
+
+ # insert an eol in place of the last space
+ text = (text[:last_abs_whitespace] + "\r\n" +
+ text[last_abs_whitespace + 1:])
# increase the absolute position because an eol is two
# characters but the space it replaced was only one
# now we're at the begining of a new line, plus the
# number of characters wrapped from the previous line
- rel_pos = 0
- for remaining_characters in text[last_whitespace:abs_pos]:
- rel_pos += glyph_columns(remaining_characters)
+ rel_pos -= last_rel_whitespace
+ last_rel_whitespace = 0
# as long as the character is not a carriage return and the
# other above conditions haven't been met, count it as a
elif each_character != "\r":
rel_pos += glyph_columns(each_character)
if unicodedata.category(each_character) in ("Cc", "Zs"):
- last_whitespace = abs_pos
+ # TODO(fungi) exclude non-breaking spaces (\x0a)
+ last_abs_whitespace = abs_pos
+ last_rel_whitespace = rel_pos
# increase the absolute position for every character
abs_pos += 1
(0, r'says, "Now less chatty\."', ""),
)
+test_wrapping = (
+ (0, '> ', "say " + 100 * "o"),
+ (1, r'says,\r\n"O[o]+\."', ""),
+)
+
test_movement = (
(0, "> ", "move north"),
(0, r"You exit to the north\.", ""),
(test_typo_replacement, "typo replacement"),
(test_sentence_capitalization, "sentence capitalization"),
(test_chat_mode, "chat mode"),
+ (test_wrapping, "wrapping"),
(test_movement, "movement"),
(test_actor_disappears, "actor spontaneous disappearance"),
(test_account1_teardown, "second account teardown"),