Have EvTable width correctly handle asian alphabets

This commit is contained in:
Griatch 2020-07-15 23:16:57 +02:00
parent a0ce1ea821
commit 101152abde
4 changed files with 74 additions and 15 deletions

View file

@ -60,6 +60,7 @@ without arguments starts a full interactive Python console.
to `spawn` command to extract the raw prototype dict for manual editing.
- `list_to_string` is now `iter_to_string` (but old name still works as legacy alias). It will
now accept any input, including generators and single values.
- EvTable should now correctly handle columns with wider asian-characters in them.

View file

@ -114,7 +114,7 @@ table string.
from django.conf import settings
from textwrap import TextWrapper
from copy import deepcopy, copy
from evennia.utils.utils import m_len, is_iter
from evennia.utils.utils import is_iter, display_len as d_len
from evennia.utils.ansi import ANSIString
_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
@ -228,7 +228,7 @@ class ANSITextWrapper(TextWrapper):
indent = self.initial_indent
# Maximum width for this line.
width = self.width - m_len(indent)
width = self.width - d_len(indent)
# First chunk on line is whitespace -- drop it, unless this
# is the very beginning of the text (ie. no lines started yet).
@ -236,7 +236,7 @@ class ANSITextWrapper(TextWrapper):
del chunks[-1]
while chunks:
l = m_len(chunks[-1])
l = d_len(chunks[-1])
# Can at least squeeze this chunk onto the current line.
if cur_len + l <= width:
@ -249,7 +249,7 @@ class ANSITextWrapper(TextWrapper):
# The current line is full, and the next chunk is too big to
# fit on *any* line (not just this one).
if chunks and m_len(chunks[-1]) > width:
if chunks and d_len(chunks[-1]) > width:
self._handle_long_word(chunks, cur_line, cur_len, width)
# If the last chunk on this line is all whitespace, drop it.
@ -439,7 +439,7 @@ class EvCell(object):
self.valign = kwargs.get("valign", "c")
self.data = self._split_lines(_to_ansi(data))
self.raw_width = max(m_len(line) for line in self.data)
self.raw_width = max(d_len(line) for line in self.data)
self.raw_height = len(self.data)
# this is extra trimming required for cels in the middle of a table only
@ -478,9 +478,9 @@ class EvCell(object):
width (int): The width to crop `text` to.
"""
if m_len(text) > width:
if d_len(text) > width:
crop_string = self.crop_string
return text[: width - m_len(crop_string)] + crop_string
return text[: width - d_len(crop_string)] + crop_string
return text
def _reformat(self):
@ -521,7 +521,7 @@ class EvCell(object):
width = self.width
adjusted_data = []
for line in data:
if 0 < width < m_len(line):
if 0 < width < d_len(line):
# replace_whitespace=False, expand_tabs=False is a
# fix for ANSIString not supporting expand_tabs/translate
adjusted_data.extend(
@ -564,7 +564,7 @@ class EvCell(object):
text (str): Centered text.
"""
excess = width - m_len(text)
excess = width - d_len(text)
if excess <= 0:
return text
if excess % 2:
@ -603,13 +603,13 @@ class EvCell(object):
if line.startswith(" ") and not line.startswith(" ")
else line
)
+ hfill_char * (width - m_len(line))
+ hfill_char * (width - d_len(line))
for line in data
]
return lines
elif align == "r":
return [
hfill_char * (width - m_len(line))
hfill_char * (width - d_len(line))
+ (
" " + line.rstrip(" ")
if line.endswith(" ") and not line.endswith(" ")
@ -750,7 +750,7 @@ class EvCell(object):
natural_width (int): Width of cell.
"""
return m_len(self.formatted[0]) # if self.formatted else 0
return d_len(self.formatted[0]) # if self.formatted else 0
def replace_data(self, data, **kwargs):
"""
@ -765,7 +765,7 @@ class EvCell(object):
"""
self.data = self._split_lines(_to_ansi(data))
self.raw_width = max(m_len(line) for line in self.data)
self.raw_width = max(d_len(line) for line in self.data)
self.raw_height = len(self.data)
self.reformat(**kwargs)

View file

@ -98,6 +98,34 @@ class TestMLen(TestCase):
self.assertEqual(utils.m_len({"hello": True, "Goodbye": False}), 2)
class TestDisplayLen(TestCase):
"""
Verifies that display_len behaves like m_len in all situations except those
where asian characters are involved.
"""
def test_non_mxp_string(self):
self.assertEqual(utils.display_len("Test_string"), 11)
def test_mxp_string(self):
self.assertEqual(utils.display_len("|lclook|ltat|le"), 2)
def test_mxp_ansi_string(self):
self.assertEqual(utils.display_len(ANSIString("|lcl|gook|ltat|le|n")), 2)
def test_non_mxp_ansi_string(self):
self.assertEqual(utils.display_len(ANSIString("|gHello|n")), 5)
def test_list(self):
self.assertEqual(utils.display_len([None, None]), 2)
def test_dict(self):
self.assertEqual(utils.display_len({"hello": True, "Goodbye": False}), 2)
def test_east_asian(self):
self.assertEqual(utils.display_len("서서서"), 6)
class TestANSIString(TestCase):
"""
Verifies that ANSIString's string-API works as intended.

View file

@ -9,7 +9,7 @@ be of use when designing your own game.
import os
import gc
import sys
import copy
import copy
import types
import math
import re
@ -20,6 +20,7 @@ import traceback
import importlib
import importlib.util
import importlib.machinery
from unicodedata import east_asian_width
from twisted.internet.task import deferLater
from twisted.internet.defer import returnValue # noqa - used as import target
from os.path import join as osjoin
@ -1819,7 +1820,7 @@ def m_len(target):
back to normal len for other objects.
Args:
target (string): A string with potential MXP components
target (str): A string with potential MXP components
to search.
Returns:
@ -1834,6 +1835,35 @@ def m_len(target):
return len(target)
def display_len(target):
"""
Calculate the 'visible width' of text. This is not necessarily the same as the
number of characters in the case of certain asian characters. This will also
strip MXP patterns.
Args:
target (string): A string with potential MXP components
to search.
Return:
int: The visible width of the target.
"""
# Would create circular import if in module root.
from evennia.utils.ansi import ANSI_PARSER
if inherits_from(target, str):
# str or ANSIString
target = ANSI_PARSER.strip_mxp(target)
target = ANSI_PARSER.parse_ansi(target, strip_ansi=True)
extra_wide = ("F", "W")
return sum(2 if east_asian_width(char) in extra_wide else 1 for char in target)
else:
return len(target)
# -------------------------------------------------------------------
# Search handler function
# -------------------------------------------------------------------