summaryrefslogtreecommitdiffstats
path: root/Demo/cwilib/vt100.py
diff options
context:
space:
mode:
Diffstat (limited to 'Demo/cwilib/vt100.py')
-rwxr-xr-xDemo/cwilib/vt100.py326
1 files changed, 326 insertions, 0 deletions
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]