Implement character manipulation APIs
This commit is contained in:
parent
3ae0e4e5ac
commit
56bff2f4a7
@ -129,7 +129,7 @@ class Line:
|
||||
((c.italic & 0b1) << ITALIC_SHIFT) | ((c.reverse & 0b1) << REVERSE_SHIFT) | ((c.strikethrough & 0b1) << STRIKE_SHIFT)
|
||||
|
||||
def apply_cursor(self, c: Cursor, at: int=0, num: int=1, clear_char=False, char=' ') -> None:
|
||||
for i in range(at, at + num):
|
||||
for i in range(at, min(len(self), at + num)):
|
||||
self.color[i] = ((c.bg & COL_MASK) << COL_SHIFT) | (c.fg & COL_MASK)
|
||||
self.decoration_fg[i] = c.decoration_fg
|
||||
sc = self.char[i]
|
||||
@ -170,6 +170,12 @@ class Line:
|
||||
for i in range(cursor.x, cursor.x + sz):
|
||||
self.combining_chars.pop(i, None)
|
||||
|
||||
def clear_text(self, start, num, clear_char=' '):
|
||||
' Clear the text in the specified range, preserving existing attributes '
|
||||
ch = ord(clear_char) & CHAR_MASK
|
||||
for i in range(start, min(len(self), start + num)):
|
||||
self.char[i] = (self.char[i] & ~CHAR_MASK) | ch
|
||||
|
||||
def copy_slice(self, src, dest, num):
|
||||
if self.combining_chars:
|
||||
scc = self.combining_chars.copy()
|
||||
@ -189,6 +195,10 @@ class Line:
|
||||
dnum = min(ls - dest_start, ls)
|
||||
if dnum:
|
||||
self.copy_slice(src_start, dest_start, dnum)
|
||||
# Check if a wide character was split at the right edge
|
||||
w = (self.char[-1] >> ATTRS_SHIFT) & 0b11
|
||||
if w != 1:
|
||||
self.char[-1] = (w << ATTRS_SHIFT) | ord(' ')
|
||||
|
||||
def left_shift(self, at: int, num: int) -> None:
|
||||
src_start, dest_start = at + num, at
|
||||
|
||||
@ -543,12 +543,13 @@ class Screen(QObject):
|
||||
y = self.cursor.y
|
||||
if top <= y <= bottom:
|
||||
x = self.cursor.x
|
||||
# TODO: Check what to do if x is on the second char of a wide char
|
||||
# pair.
|
||||
num = min(self.columns - x, count)
|
||||
line = self.linebuf[y]
|
||||
# TODO: Handle wide chars that get split at the right edge.
|
||||
line.right_shift(x, num)
|
||||
line.apply_cursor(self.cursor, x, num, clear_char=True)
|
||||
self.update_cell_range(y, x, self.columns)
|
||||
self.update_cell_range(y, x, self.columns - 1)
|
||||
|
||||
def delete_characters(self, count=1):
|
||||
"""Deletes the indicated # of characters, starting with the
|
||||
@ -565,15 +566,19 @@ class Screen(QObject):
|
||||
if top <= y <= bottom:
|
||||
x = self.cursor.x
|
||||
num = min(self.columns - x, count)
|
||||
# TODO: Handle deletion of wide chars
|
||||
# TODO: Check if we need to count wide chars as one or two chars
|
||||
# for this control code. Also, what happens if we start deleting
|
||||
# at the second cell of a wide character, or delete only the first
|
||||
# cell of a wide character?
|
||||
line = self.linebuf[y]
|
||||
line.left_shift(x, num)
|
||||
line.apply_cursor(self.cursor, self.columns - num, num, clear_char=True)
|
||||
self.update_cell_range(y, x, self.columns - 1)
|
||||
|
||||
def erase_characters(self, count=None):
|
||||
def erase_characters(self, count=1):
|
||||
"""Erases the indicated # of characters, starting with the
|
||||
character at cursor position. Character attributes are set
|
||||
cursor attributes. The cursor remains in the same position.
|
||||
to cursor attributes. The cursor remains in the same position.
|
||||
|
||||
:param int count: number of characters to erase.
|
||||
|
||||
@ -585,10 +590,11 @@ class Screen(QObject):
|
||||
to all ``erase_*()`` and ``delete_*()`` methods.
|
||||
"""
|
||||
count = count or 1
|
||||
|
||||
for column in range(self.cursor.x,
|
||||
min(self.cursor.x + count, self.columns)):
|
||||
self.buffer[self.cursor.y][column] = self.cursor.attrs
|
||||
x, y = self.cursor.x, self.cursor.y
|
||||
# TODO: Same set of wide character questions as for delete_characters()
|
||||
num = min(self.columns - x, count)
|
||||
self.linebuf[y].apply_cursor(self.cursor, x, num, clear_char=True)
|
||||
self.update_cell_range(y, x, min(x + num, self.columns) - 1)
|
||||
|
||||
def erase_in_line(self, how=0, private=False):
|
||||
"""Erases a line in a specific way.
|
||||
@ -601,22 +607,30 @@ class Screen(QObject):
|
||||
including cursor position.
|
||||
* ``2`` -- Erases complete line.
|
||||
:param bool private: when ``True`` character attributes are left
|
||||
unchanged **not implemented**.
|
||||
unchanged.
|
||||
"""
|
||||
s = n = 0
|
||||
if how == 0:
|
||||
# a) erase from the cursor to the end of line, including
|
||||
# the cursor,
|
||||
interval = range(self.cursor.x, self.columns)
|
||||
s, n = self.cursor.x, self.columns - self.cursor.x
|
||||
elif how == 1:
|
||||
# b) erase from the beginning of the line to the cursor,
|
||||
# including it,
|
||||
interval = range(self.cursor.x + 1)
|
||||
s, n = 0, self.cursor.x + 1
|
||||
elif how == 2:
|
||||
# c) erase the entire line.
|
||||
interval = range(self.columns)
|
||||
|
||||
for column in interval:
|
||||
self.buffer[self.cursor.y][column] = self.cursor.attrs
|
||||
s, n = 0, self.columns
|
||||
if n - s:
|
||||
# TODO: Same set of questions as for delete_characters()
|
||||
y = self.cursor.y
|
||||
line = self.linebuf[y]
|
||||
c = None if private else self.cursor
|
||||
if private:
|
||||
line.clear_text(s, n)
|
||||
else:
|
||||
line.apply_cursor(c, s, n, clear_char=True)
|
||||
self.update_cell_range(y, s, min(s + n, self.columns - 1))
|
||||
|
||||
def erase_in_display(self, how=0, private=False):
|
||||
"""Erases display in a specific way.
|
||||
|
||||
@ -100,3 +100,64 @@ class TestScreen(BaseTest):
|
||||
self.ae(str(s.linebuf[4]), 'a\u0306b1\u030623')
|
||||
self.ae((s.cursor.x, s.cursor.y), (2, 4))
|
||||
self.assertChanges(t, ignore='cursor', cells={4: ((0, 4),)})
|
||||
|
||||
def test_char_manipulation(self):
|
||||
s, t = self.create_screen()
|
||||
|
||||
def init():
|
||||
s.reset(), t.reset()
|
||||
s.draw(b'abcde')
|
||||
s.cursor.bold = True
|
||||
s.cursor_back(4)
|
||||
t.reset()
|
||||
self.ae(s.cursor.x, 1)
|
||||
|
||||
init()
|
||||
s.insert_characters(2)
|
||||
self.ae(str(s.linebuf[0]), 'a bc')
|
||||
self.assertTrue(s.linebuf[0].cursor_from(1).bold)
|
||||
self.assertChanges(t, ignore='cursor', cells={0: ((1, 4),)})
|
||||
s.cursor_back(1)
|
||||
s.insert_characters(20)
|
||||
self.ae(str(s.linebuf[0]), ' ')
|
||||
self.assertChanges(t, ignore='cursor', cells={0: ((0, 4),)})
|
||||
s.draw('xココ'.encode('utf-8'))
|
||||
s.cursor_back(5)
|
||||
t.reset()
|
||||
s.insert_characters(1)
|
||||
self.ae(str(s.linebuf[0]), ' xコ ')
|
||||
self.assertChanges(t, ignore='cursor', cells={0: ((0, 4),)})
|
||||
|
||||
init()
|
||||
s.delete_characters(2)
|
||||
self.ae(str(s.linebuf[0]), 'ade ')
|
||||
self.assertTrue(s.linebuf[0].cursor_from(4).bold)
|
||||
self.assertFalse(s.linebuf[0].cursor_from(2).bold)
|
||||
self.assertChanges(t, ignore='cursor', cells={0: ((1, 4),)})
|
||||
|
||||
init()
|
||||
s.erase_characters(2)
|
||||
self.ae(str(s.linebuf[0]), 'a de')
|
||||
self.assertTrue(s.linebuf[0].cursor_from(1).bold)
|
||||
self.assertFalse(s.linebuf[0].cursor_from(4).bold)
|
||||
self.assertChanges(t, cells={0: ((1, 2),)})
|
||||
s.erase_characters(20)
|
||||
self.ae(str(s.linebuf[0]), 'a ')
|
||||
|
||||
init()
|
||||
s.erase_in_line()
|
||||
self.ae(str(s.linebuf[0]), 'a ')
|
||||
self.assertTrue(s.linebuf[0].cursor_from(1).bold)
|
||||
self.assertFalse(s.linebuf[0].cursor_from(0).bold)
|
||||
self.assertChanges(t, cells={0: ((1, 4),)})
|
||||
init()
|
||||
s.erase_in_line(1)
|
||||
self.ae(str(s.linebuf[0]), ' cde')
|
||||
self.assertChanges(t, cells={0: ((0, 2),)})
|
||||
init()
|
||||
s.erase_in_line(2)
|
||||
self.ae(str(s.linebuf[0]), ' ')
|
||||
self.assertChanges(t, cells={0: ((0, 4),)})
|
||||
init()
|
||||
s.erase_in_line(2, private=True)
|
||||
self.ae((False, False, False, False, False), tuple(map(lambda i: s.linebuf[0].cursor_from(i).bold, range(5))))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user