diff options
Diffstat (limited to 'Demo/cwilib/vt100.py')
-rwxr-xr-x | Demo/cwilib/vt100.py | 326 |
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] |