Fix ANSIString parsing on partial slice from start/end of string. Resolve #2205.

This commit is contained in:
Griatch 2020-10-26 21:35:15 +01:00
parent c30e2a4629
commit 558a3d76c7
3 changed files with 81 additions and 4 deletions

View file

@ -526,6 +526,13 @@ def raw(string):
return string.replace("{", "{{").replace("|", "||")
# ------------------------------------------------------------
#
# ANSIString - ANSI-aware string class
#
# ------------------------------------------------------------
def _spacing_preflight(func):
"""
This wrapper function is used to do some preflight checks on
@ -896,9 +903,23 @@ class ANSIString(str, metaclass=ANSIMeta):
replayed.
"""
slice_indexes = self._char_indexes[slc]
char_indexes = self._char_indexes
slice_indexes = char_indexes[slc]
# If it's the end of the string, we need to append final color codes.
if not slice_indexes:
# if we find no characters it may be because we are just outside
# of the interval, using an open-ended slice. We must replay all
# of the escape characters until/after this point.
if char_indexes:
if slc.start is None and slc.stop is None:
# a [:] slice of only escape characters
return ANSIString(self._raw_string[slc])
if slc.start is None:
# this is a [:x] slice
return ANSIString(self._raw_string[:char_indexes[0]])
if slc.stop is None:
# a [x:] slice
return ANSIString(self._raw_string[char_indexes[-1] + 1:])
return ANSIString("")
try:
string = self[slc.start or 0]._raw_string
@ -918,7 +939,7 @@ class ANSIString(str, metaclass=ANSIMeta):
# raw_string not long enough
pass
if i is not None:
append_tail = self._get_interleving(self._char_indexes.index(i) + 1)
append_tail = self._get_interleving(char_indexes.index(i) + 1)
else:
append_tail = ""
return ANSIString(string + append_tail, decoded=True)

View file

@ -165,9 +165,12 @@ def _to_rect(lines):
def _to_ansi(obj, regexable=False):
"convert to ANSIString"
if isinstance(obj, str):
if isinstance(obj, ANSIString):
return obj
elif isinstance(obj, str):
# since ansi will be parsed twice (here and in the normal ansi send), we have to
# escape the |-structure twice.
# escape the |-structure twice. TODO: This is tied to the default color-tag syntax
# which is not ideal for those wanting to replace/extend it ...
obj = _ANSI_ESCAPE.sub(r"||||", obj)
if isinstance(obj, dict):
return dict((key, _to_ansi(value, regexable=regexable)) for key, value in obj.items())

View file

@ -177,6 +177,59 @@ class ANSIStringTestCase(TestCase):
self.assertEqual(a.rstrip(), ANSIString(" |r Test of stuff |b with spaces|n"))
self.assertEqual(b.strip(), b)
def test_regex_search(self):
"""
Test regex-search in ANSIString - the found position should ignore any ansi-markers
"""
string = ANSIString(" |r|[b Test ")
match = re.search(r"Test", string)
self.assertTrue(match)
self.assertEqual(match.span(), (3, 7))
def test_regex_replace(self):
"""
Inserting text into an ansistring at an index position should ignore
the ansi markers but not remove them!
"""
string = ANSIString("A |rTest|n string")
match = re.search(r"Test", string)
ix1, ix2 = match.span()
self.assertEqual((ix1, ix2), (2, 6))
result = string[:ix1] + "Replacement" + string[ix2:]
expected = ANSIString("A |rReplacement|n string")
self.assertEqual(expected, result)
def test_slice_insert(self):
"""
Inserting a slice should not remove ansi markup (issue #2205)
"""
string = ANSIString("|rTest|n")
split_string = string[:0] + "Test" + string[4:]
self.assertEqual(string.raw(), split_string.raw())
def test_slice_insert_longer(self):
"""
The ANSIString replays the color code before the split in order to
produce a *visually* identical result. The result is a longer string in
raw characters, but one which correctly represents the color output.
"""
string = ANSIString("A bigger |rTest|n of things |bwith more color|n")
# from evennia import set_trace;set_trace()
split_string = string[:9] + "Test" + string[13:]
self.assertEqual(
repr((ANSIString("A bigger ")
+ ANSIString("|rTest") # note that the |r|n is replayed together on next line
+ ANSIString("|r|n of things |bwith more color|n")).raw()),
repr(split_string.raw()))
def test_slice_full(self):
string = ANSIString("A bigger |rTest|n of things |bwith more color|n")
split_string = string[:]
self.assertEqual(string.raw(), split_string.raw())
class TestTextToHTMLparser(TestCase):
def setUp(self):