Skip to content

Commit 020fce6

Browse files
committed
Cache in Char its width
Instead of computing it on each screen.display, compute the width of the char once on screen.draw and store it in the Char tuple. This makes screen.display ~x1.10 to ~x1.20 faster and it makes stream.feed only ~x1.01 slower in the worst case. This negative impact is due the change on screen.draw but measurements on my lab show inconsistent results (stream.feed didn't show a consistent performance regression and ~x1.01 slower was the worst value that I've got).
1 parent de59245 commit 020fce6

File tree

2 files changed

+25
-11
lines changed

2 files changed

+25
-11
lines changed

pyte/screens.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ class Char(namedtuple("Char", [
7171
"strikethrough",
7272
"reverse",
7373
"blink",
74+
"width",
7475
])):
7576
"""A single styled on-screen character.
7677
@@ -89,15 +90,16 @@ class Char(namedtuple("Char", [
8990
during rendering. Defaults to ``False``.
9091
:param bool blink: flag for rendering the character blinked. Defaults to
9192
``False``.
93+
:param bool width: the width in terms of cells to display this char.
9294
"""
9395
__slots__ = ()
9496

95-
def __new__(cls, data, fg="default", bg="default", bold=False,
97+
def __new__(cls, data=" ", fg="default", bg="default", bold=False,
9698
italics=False, underscore=False,
97-
strikethrough=False, reverse=False, blink=False):
99+
strikethrough=False, reverse=False, blink=False, width=wcwidth(" ")):
98100
return super(Char, cls).__new__(cls, data, fg, bg, bold, italics,
99101
underscore, strikethrough, reverse,
100-
blink)
102+
blink, width)
101103

102104

103105
class Cursor:
@@ -111,7 +113,7 @@ class Cursor:
111113
"""
112114
__slots__ = ("x", "y", "attrs", "hidden")
113115

114-
def __init__(self, x, y, attrs=Char(" ")):
116+
def __init__(self, x, y, attrs=Char(" ", width=wcwidth(" "))):
115117
self.x = x
116118
self.y = y
117119
self.attrs = attrs
@@ -211,7 +213,7 @@ class Screen:
211213
def default_char(self):
212214
"""An empty character with default foreground and background colors."""
213215
reverse = mo.DECSCNM in self.mode
214-
return Char(data=" ", fg="default", bg="default", reverse=reverse)
216+
return Char(data=" ", fg="default", bg="default", reverse=reverse, width=wcwidth(" "))
215217

216218
def __init__(self, columns, lines):
217219
self.savepoints = []
@@ -256,7 +258,7 @@ def display(self):
256258
is_wide_char = False
257259
continue
258260
char = cell.data
259-
is_wide_char = wcwidth(char[0]) == 2
261+
is_wide_char = cell.width == 2
260262
display_line.append(char)
261263

262264
gap = columns - (prev_x + 1)
@@ -527,16 +529,18 @@ def draw(self, data):
527529

528530
line = self.buffer[self.cursor.y]
529531
if char_width == 1:
530-
line[self.cursor.x] = self.cursor.attrs._replace(data=char)
532+
line[self.cursor.x] = self.cursor.attrs._replace(data=char, width=char_width)
531533
elif char_width == 2:
532534
# A two-cell character has a stub slot after it.
533-
line[self.cursor.x] = self.cursor.attrs._replace(data=char)
535+
line[self.cursor.x] = self.cursor.attrs._replace(data=char, width=char_width)
534536
if self.cursor.x + 1 < self.columns:
535537
line[self.cursor.x + 1] = self.cursor.attrs \
536-
._replace(data="")
538+
._replace(data="", width=0)
537539
elif char_width == 0 and unicodedata.combining(char):
538540
# A zero-cell character is combined with the previous
539541
# character either on this or preceding line.
542+
# Because char's width is zero, this will not change the width
543+
# of the previous character.
540544
if self.cursor.x:
541545
last = line[self.cursor.x - 1]
542546
normalized = unicodedata.normalize("NFC", last.data + char)

tests/helpers/asserts.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,17 @@ def consistency_asserts(screen):
66
# width of the first code point.
77
for y in range(screen.lines):
88
for x in range(screen.columns):
9-
char = screen.buffer[y][x].data
10-
assert sum(map(wcwidth, char[1:])) == 0
9+
data = screen.buffer[y][x].data
10+
assert sum(map(wcwidth, data[1:])) == 0
1111

1212

13+
# Ensure consistency between the real width (computed here
14+
# with wcwidth(...)) and the char.width attribute
15+
for y in range(screen.lines):
16+
for x in range(screen.columns):
17+
char = screen.buffer[y][x]
18+
if char.data:
19+
assert wcwidth(char.data[0]) == char.width
20+
else:
21+
assert char.data == ""
22+
assert char.width == 0

0 commit comments

Comments
 (0)