1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
|
# 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.msg('send: unfinished=%s, buffer=%s',
`self.unfinished`, `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) - 2
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]
|