Initial revision
diff --git a/Demo/cwilib/vt100.py b/Demo/cwilib/vt100.py
new file mode 100755
index 0000000..e802389
--- /dev/null
+++ b/Demo/cwilib/vt100.py
@@ -0,0 +1,326 @@
+# VT100 terminal emulator.
+# This is incomplete and slow, but will do for now...
+# It shouldn't be difficult to extend it to be a more-or-less complete
+# VT100 emulator.  And little bit of profiling could go a long way...
+
+from array import array
+import regex
+import string
+
+# Tunable parameters
+DEBUGLEVEL = 1
+
+# Symbolic constants
+ESC = '\033'
+
+
+# VT100 emulation class
+
+class VT100:
+
+	def __init__(self):
+		self.debuglevel = DEBUGLEVEL
+		# Unchangeable parameters (for now)
+		self.width = 80
+		self.height = 24
+		self.blankline = array('c', ' '*self.width)
+		self.blankattr = array('b', '\0'*self.width)
+		# Set mutable display state
+		self.reset()
+		# Set parser state
+		self.unfinished = ''
+		# Set screen recognition state
+		self.reset_recognizer()
+
+	def msg(self, msg, *args):
+		if self.debuglevel > 0:
+			print 'VT100:', msg%args
+
+	def set_debuglevel(self, debuglevel):
+		self.debuglevel = debuglevel
+
+	def reset(self):
+		self.lines = []
+		self.attrs = []
+		self.fill_bottom()
+		self.x = 0
+		self.y = 0
+		self.curattrs = []
+
+	def show(self):
+		lineno = 0
+		for line in self.lines:
+			lineno = lineno + 1
+			i = len(line)
+			while i > 0 and line[i-1] == ' ': i = i-1
+			print line[:i]
+			print 'CURSOR:', self.x, self.y
+
+	def fill_bottom(self):
+		while len(self.lines) < self.height:
+			self.lines.append(self.blankline[:])
+			self.attrs.append(self.blankattr[:])
+
+	def fill_top(self):
+		while len(self.lines) < self.height:
+			self.lines.insert(0, self.blankline[:])
+			self.attrs.insert(0, self.blankattr[:])
+
+	def clear_all(self):
+		self.lines = []
+		self.attrs = []
+		self.fill_bottom()
+
+	def clear_below(self):
+		del self.lines[self.y:]
+		del self.attrs[self.y:]
+		self.fill_bottom()
+
+	def clear_above(self):
+		del self.lines[:self.y]
+		del self.attrs[:self.y]
+		self.fill_top()
+
+	def send(self, buffer):
+		self.unfinished = self.unfinished + buffer
+		i = 0
+		n = len(self.unfinished)
+		while i < n:
+			c = self.unfinished[i]
+			i = i+1
+			if c != ESC:
+				self.add_char(c)
+				continue
+			if i >= n:
+				i = i-1
+				break
+			c = self.unfinished[i]
+			i = i+1
+			if c == 'c':
+				self.reset()
+				continue
+			if c <> '[':
+				self.msg('unrecognized: ESC %s', `c`)
+				continue
+			argstr = ''
+			while i < n:
+				c = self.unfinished[i]
+				i = i+1
+				if c not in '0123456789;':
+					break
+				argstr = argstr + c
+			else:
+				i = i - len(argstr)
+				break
+##			self.msg('found ESC [ %s %s' % (`argstr`, `c`))
+			args = string.splitfields(argstr, ';')
+			for j in range(len(args)):
+				s = args[j]
+				while s[:1] == '0': s = s[1:]
+				if s: args[j] = eval(s)
+				else: args[j] = 0
+			p1 = p2 = 0
+			if args: p1 = args[0]
+			if args[1:]: p2 = args[1]
+			if c in '@ABCDH':
+				if not p1: p1 = 1
+			if c in 'H':
+				if not p2: p2 = 1
+			if c == '@':
+				for j in range(p1):
+					self.add_char(' ')
+			elif c == 'A':
+				self.move_by(0, -p1)
+			elif c == 'B':
+				self.move_by(0, p1)
+			elif c == 'C':
+				self.move_by(p1, 0)
+			elif c == 'D':
+				self.move_by(-p1, 0)
+			elif c == 'H':
+				self.move_to(p2-1, p1-1)
+			elif c == 'J':
+				if p1 == 0: self.clear_above()
+				elif p1 == 1: self.clear_below()
+				elif p1 == 2: self.clear_all()
+				else: self.msg('weird ESC [ %d J', p1)
+			elif c == 'K':
+				if p1 == 0: self.erase_right()
+				elif p1 == 1: self.erase_left()
+				elif p1 == 2: self.erase_line()
+				else: self.msg('weird ESC [ %d K', p1)
+			elif c == 'm':
+				if p1 == 0:
+					self.curattrs = []
+				else:
+					if p1 not in self.curattrs:
+						self.curattrs.append(p1)
+						self.curattrs.sort()
+			else:
+				self.msg('unrecognized: ESC [ %s', `argstr+c`)
+		self.unfinished = self.unfinished[i:]
+
+	def add_char(self, c):
+		if c == '\r':
+			self.move_to(0, self.y)
+			return
+		if c in '\n\f\v':
+			self.move_to(self.x, self.y + 1)
+			if self.y >= self.height:
+				self.scroll_up(1)
+				self.move_to(self.x, self.height - 1)
+			return
+		if c == '\b':
+			self.move_by(-1, 0)
+			return
+		if c == '\a':
+			self.msg('BELL')
+			return
+		if c == '\t':
+			self.move_to((self.x+8)/8*8, self.y)
+			return
+		if c == '\0':
+			return
+		if c < ' ' or c > '~':
+			self.msg('ignored control char: %s', `c`)
+			return
+		if self.x >= self.width:
+			self.move_to(0, self.y + 1)
+		if self.y >= self.height:
+			self.scroll_up(1)
+			self.move_to(self.x, self.height - 1)
+		self.lines[self.y][self.x] = c
+		if self.curattrs:
+			self.attrs[self.y][self.x] = max(self.curattrs)
+		else:
+			self.attrs[self.y][self.x] = 0
+		self.move_by(1, 0)
+
+	def move_to(self, x, y):
+		self.x = min(max(0, x), self.width)
+		self.y = min(max(0, y), self.height)
+
+	def move_by(self, dx, dy):
+		self.move_to(self.x + dx, self.y + dy)
+
+	def scroll_up(self, nlines):
+		del self.lines[:max(0, nlines)]
+		del self.attrs[:max(0, nlines)]
+		self.fill_bottom()
+
+	def scroll_down(self, nlines):
+		del self.lines[-max(0, nlines):]
+		del self.attrs[-max(0, nlines):]
+		self.fill_top()
+
+	def erase_left(self):
+		x = min(self.width-1, x)
+		y = min(self.height-1, y)
+		self.lines[y][:x] = self.blankline[:x]
+		self.attrs[y][:x] = self.blankattr[:x]
+
+	def erase_right(self):
+		x = min(self.width-1, x)
+		y = min(self.height-1, y)
+		self.lines[y][x:] = self.blankline[x:]
+		self.attrs[y][x:] = self.blankattr[x:]
+
+	def erase_line(self):
+		self.lines[y][:] = self.blankline
+		self.attrs[y][:] = self.blankattr
+
+	# The following routines help automating the recognition of
+	# standard screens.  A standard screen is characterized by
+	# a number of fields.  A field is part of a line,
+	# characterized by a (lineno, begin, end) tuple;
+	# e.g. the first 10 characters of the second line are
+	# specified by the tuple (1, 0, 10).  Fields can be:
+	# - regex: desired contents given by a regular expression,
+	# - extract: can be extracted,
+	# - cursor: screen is only valid if cursor in field,
+	# - copy: identical to another screen (position is ignored).
+	# A screen is defined as a dictionary full of fields.  Screens
+	# also have names and are placed in a dictionary.
+
+	def reset_recognizer(self):
+		self.screens = {}
+
+	def define_screen(self, screenname, fields):
+		fieldscopy = {}
+		# Check if the fields make sense
+		for fieldname in fields.keys():
+			field = fields[fieldname]
+			ftype, lineno, begin, end, extra = field
+			if ftype in ('match', 'search'):
+				extra = regex.compile(extra)
+			elif ftype == 'extract':
+				extra = None
+			elif ftype == 'cursor':
+				extra = None
+			elif ftype == 'copy':
+				if not self.screens.has_key(extra):
+					raise ValueError, 'bad copy ref'
+			else:
+				raise ValueError, 'bad ftype: %s' % `ftype`
+			fieldscopy[fieldname] = (
+				  ftype, lineno, begin, end, extra)
+		self.screens[screenname] = fieldscopy
+
+	def which_screens(self):
+		self.busy = []
+		self.okay = []
+		self.fail = []
+		for name in self.screens.keys():
+			ok = self.match_screen(name)
+		return self.okay[:]
+
+	def match_screen(self, name):
+		if name in self.busy: raise RuntimeError, 'recursive match'
+		if name in self.okay: return 1
+		if name in self.fail: return 0
+		self.busy.append(name)
+		fields = self.screens[name]
+		ok = 0
+		for key in fields.keys():
+			field = fields[key]
+			ftype, lineno, begin, end, extra = field
+			if ftype == 'copy':
+				if not self.match_screen(extra): break
+			elif ftype == 'search':
+				text = self.lines[lineno][begin:end].tostring()
+				if extra.search(text) < 0:
+					break
+			elif ftype == 'match':
+				text = self.lines[lineno][begin:end].tostring()
+				if extra.match(text) < 0:
+					break
+			elif ftype == 'cursor':
+				if self.x != lineno or not \
+					  begin <= self.y < end:
+					break
+		else:
+			ok = 1
+		if ok:
+			self.okay.append(name)
+		else:
+			self.fail.append(name)
+		self.busy.remove(name)
+		return ok
+
+	def extract_field(self, screenname, fieldname):
+		ftype, lineno, begin, end, extra = \
+			  self.screens[screenname][fieldname]
+		return stripright(self.lines[lineno][begin:end].tostring())
+
+	def extract_rect(self, left, top, right, bottom):
+		lines = []
+		for i in range(top, bottom):
+			lines.append(stripright(self.lines[i][left:right])
+				  .tostring())
+		return lines
+
+
+def stripright(line):
+	i = len(line)
+	while i > 0 and line[i-1] in string.whitespace: i = i-1
+	return line[:i]