Skip to content

Add width awareness to fix wrapping of strings with full width chars #839

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 6, 2020
Merged
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
25 changes: 22 additions & 3 deletions bpython/curtsiesfrontend/repl.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ def __init__(
# so we're just using the same object
self.interact = self.status_bar

# line currently being edited, without ps1 (usually '>>> ')
# logical line currently being edited, without ps1 (usually '>>> ')
self._current_line = ""

# current line of output - stdout and stdin go here
Expand Down Expand Up @@ -744,8 +744,9 @@ def process_key_event(self, e):
)
and self.config.curtsies_right_arrow_completion
and self.cursor_offset == len(self.current_line)
# if at end of current line and user presses RIGHT (to autocomplete)
):

# then autocomplete
self.current_line += self.current_suggestion
self.cursor_offset = len(self.current_line)
elif e in ("<UP>",) + key_dispatch[self.config.up_one_line_key]:
Expand Down Expand Up @@ -1435,6 +1436,23 @@ def current_output_line(self, value):
self.current_stdouterr_line = ""
self.stdin.current_line = "\n"

def number_of_padding_chars_on_current_cursor_line(self):
""" To avoid cutting off two-column characters at the end of lines where
there's only one column left, curtsies adds a padding char (u' ').
It's important to know about these for cursor positioning.

Should return zero unless there are fullwidth characters. """
full_line = self.current_cursor_line_without_suggestion
line_with_padding = "".join(
line.s
for line in paint.display_linize(
self.current_cursor_line_without_suggestion.s, self.width
)
)

# the difference in length here is how much padding there is
return len(line_with_padding) - len(full_line)

def paint(
self,
about_to_exit=False,
Expand Down Expand Up @@ -1620,7 +1638,8 @@ def move_screen_up(current_line_start_row):
wcswidth(self.current_cursor_line_without_suggestion.s)
- wcswidth(self.current_line)
+ wcswidth(self.current_line[: self.cursor_offset])
),
)
+ self.number_of_padding_chars_on_current_cursor_line(),
width,
)
assert cursor_column >= 0, (
Expand Down
28 changes: 16 additions & 12 deletions bpython/curtsiesfrontend/replpainter.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,24 @@ def display_linize(msg, columns, blank_line=False):
"""Returns lines obtained by splitting msg over multiple lines.

Warning: if msg is empty, returns an empty list of lines"""
display_lines = (
[
msg[start:end]
for start, end in zip(
range(0, len(msg), columns),
range(columns, len(msg) + columns, columns),
)
]
if msg
else ([""] if blank_line else [])
)
if not msg:
return [''] if blank_line else []
msg = fmtstr(msg)
try:
display_lines = list(msg.width_aware_splitlines(columns))
# use old method if wcwidth can't determine width of msg
except ValueError:
display_lines = (
[
msg[start:end]
for start, end in zip(
range(0, len(msg), columns),
range(columns, len(msg) + columns, columns),
)
]
)
return display_lines


def paint_history(rows, columns, display_lines):
lines = []
for r, line in zip(range(rows), display_lines[-rows:]):
Expand Down
26 changes: 26 additions & 0 deletions bpython/test/test_curtsies_painting.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,32 @@ def send_key(self, key):
self.repl.process_event("<SPACE>" if key == " " else key)
self.repl.paint() # has some side effects we need to be wary of

class TestWidthAwareness(HigherLevelCurtsiesPaintingTest):

def test_cursor_position_with_fullwidth_char(self):
self.repl.add_normal_character("間")

cursor_pos = self.repl.paint()[1]
self.assertEqual(cursor_pos, (0,6))

def test_cursor_position_with_padding_char(self):
# odd numbered so fullwidth chars don't wrap evenly
self.repl.width = 11
[self.repl.add_normal_character(c) for c in "width"]

cursor_pos = self.repl.paint()[1]
self.assertEqual(cursor_pos, (1,4))

def test_display_of_padding_chars(self):
self.repl.width = 11
[self.repl.add_normal_character(c) for c in "width"]

self.enter()
expected = [
'>>> wid ', # <--- note the added trailing space
'th']
result = [d.s for d in self.repl.display_lines[0:2]]
self.assertEqual(result, expected)

class TestCurtsiesRewindRedraw(HigherLevelCurtsiesPaintingTest):
def test_rewind(self):
Expand Down