# Represent colourised terminal text in a width-independent form.
import html.parser
+import io
+import itertools
import time
import wcwidth
+class ColouredString:
+ def __init__(self, string, colour=' '):
+ if isinstance(string, ColouredString):
+ self.s, self.c = string.s, string.c
+ else:
+ if len(colour) != len(string):
+ assert len(colour) == 1, "Colour ids are single characters"
+ colour = colour * len(string)
+ self.s, self.c = string, colour
+
+ def __add__(self, rhs):
+ rhs = type(self)(rhs)
+ return type(self)(self.s + rhs.s, self.c + rhs.c)
+
+ def __mul__(self, rhs):
+ return type(self)(self.s * rhs, self.c * rhs)
+
+ def __len__(self):
+ return len(self.s)
+
+ @property
+ def width(self):
+ return wcwidth.wcswidth(self.s)
+
+ def __str__(self):
+ return self.s
+
+ def __repr__(self):
+ return f"ColouredString({self.s!r}, {self.c!r})"
+
+ def __eq__(self, rhs):
+ rhs = type(self)(rhs)
+ return (self.s, self.c) == (rhs.s, rhs.c)
+ def __ne__(self, rhs):
+ return not (self == rhs)
+
+ def ecma48(self):
+ buf = io.StringIO()
+ colour = ' '
+ for sc, cc in itertools.chain(zip(self.s, self.c), [('',' ')]):
+ if cc != colour:
+ buf.write({
+ ' ': '\033[m',
+ 'S': '\033[0;1;7;44;37m',
+ 'D': '\033[0;7;44;37m',
+ 'F': '\033[0;1;32m',
+ }[cc])
+ colour = cc
+ buf.write(sc)
+ return buf.getvalue()
+
class BlankLine:
def render(self, width):
- yield ""
+ yield ColouredString("")
class SeparatorLine:
def __init__(self, timestamp):
def render(self, width):
date = time.strftime("%a %b %e %H:%M:%S %Y",
time.localtime(self.timestamp))
- # FIXME: colours
- suffix = "[" + date + "]--"
- yield "-" * (width - 1 - len(suffix)) + suffix
+ suffix = (ColouredString("[", 'S') +
+ ColouredString(date, 'D') +
+ ColouredString("]--", 'S'))
+ yield ColouredString("-", 'S') * (width - 1 - suffix.width) + suffix
class FromLine:
def __init__(self, account, nameline):
def render(self, width):
# FIXME: truncate
-
- yield f"From: {self.nameline} ({self.account})"
+ yield (ColouredString("From: ") +
+ ColouredString(f"{self.nameline} ({self.account})", 'F'))
class Paragraph:
def __init__(self):
self.words = []
- self.unfinished_word = ''
+ self.unfinished_word = ColouredString('')
def render(self, width):
# For the moment, greedy algorithm. We can worry about cleverness later
- line, space = '', ''
+ line, space = ColouredString(''), ColouredString('')
for word in self.words:
- if line != "" and wcwidth.wcswidth(line + space + word) >= width:
+ if line != "" and (line + space + word).width >= width:
yield line
- line, space = '', ''
+ line, space = ColouredString(''), ColouredString('')
line += space + word
- space = ' '
+ space = ColouredString(' ')
- if wcwidth.wcswidth(line) >= width:
+ if line.width >= width:
# FIXME: wrap explicitly?
yield line
- line, space = '', ''
+ line, space = ColouredString(''), ColouredString('')
- if line != "":
+ if len(line) != 0:
yield line
def empty(self):
def end_word(self):
if len(self.unfinished_word) > 0:
self.words.append(self.unfinished_word)
- self.unfinished_word = ''
+ self.unfinished_word = ColouredString('')
def add(self, text):
for c in text: