1 # VT100 terminal emulator.
2 # This is incomplete and slow, but will do for now...
3 # It shouldn't be difficult to extend it to be a more-or-less complete
4 # VT100 emulator. And little bit of profiling could go a long way...
6 from array
import array
17 # VT100 emulation class
22 self
.debuglevel
= DEBUGLEVEL
23 # Unchangeable parameters (for now)
26 self
.blankline
= array('c', ' '*self
.width
)
27 self
.blankattr
= array('b', '\0'*self
.width
)
28 # Set mutable display state
32 # Set screen recognition state
33 self
.reset_recognizer()
35 def msg(self
, msg
, *args
):
36 if self
.debuglevel
> 0:
37 print 'VT100:', msg
%args
39 def set_debuglevel(self
, debuglevel
):
40 self
.debuglevel
= debuglevel
52 for line
in self
.lines
:
55 while i
> 0 and line
[i
-1] == ' ': i
= i
-1
57 print 'CURSOR:', self
.x
, self
.y
59 def fill_bottom(self
):
60 while len(self
.lines
) < self
.height
:
61 self
.lines
.append(self
.blankline
[:])
62 self
.attrs
.append(self
.blankattr
[:])
65 while len(self
.lines
) < self
.height
:
66 self
.lines
.insert(0, self
.blankline
[:])
67 self
.attrs
.insert(0, self
.blankattr
[:])
74 def clear_below(self
):
75 del self
.lines
[self
.y
:]
76 del self
.attrs
[self
.y
:]
79 def clear_above(self
):
80 del self
.lines
[:self
.y
]
81 del self
.attrs
[:self
.y
]
84 def send(self
, buffer):
85 self
.msg('send: unfinished=%s, buffer=%s',
86 `self
.unfinished`
, `
buffer`
)
87 self
.unfinished
= self
.unfinished
+ buffer
89 n
= len(self
.unfinished
)
91 c
= self
.unfinished
[i
]
99 c
= self
.unfinished
[i
]
105 self
.msg('unrecognized: ESC %s', `c`
)
109 c
= self
.unfinished
[i
]
111 if c
not in '0123456789;':
115 i
= i
- len(argstr
) - 2
117 ## self.msg('found ESC [ %s %s' % (`argstr`, `c`))
118 args
= string
.splitfields(argstr
, ';')
119 for j
in range(len(args
)):
121 while s
[:1] == '0': s
= s
[1:]
122 if s
: args
[j
] = eval(s
)
125 if args
: p1
= args
[0]
126 if args
[1:]: p2
= args
[1]
143 self
.move_to(p2
-1, p1
-1)
145 if p1
== 0: self
.clear_above()
146 elif p1
== 1: self
.clear_below()
147 elif p1
== 2: self
.clear_all()
148 else: self
.msg('weird ESC [ %d J', p1
)
150 if p1
== 0: self
.erase_right()
151 elif p1
== 1: self
.erase_left()
152 elif p1
== 2: self
.erase_line()
153 else: self
.msg('weird ESC [ %d K', p1
)
158 if p1
not in self
.curattrs
:
159 self
.curattrs
.append(p1
)
162 self
.msg('unrecognized: ESC [ %s', `argstr
+c`
)
163 self
.unfinished
= self
.unfinished
[i
:]
165 def add_char(self
, c
):
167 self
.move_to(0, self
.y
)
170 self
.move_to(self
.x
, self
.y
+ 1)
171 if self
.y
>= self
.height
:
173 self
.move_to(self
.x
, self
.height
- 1)
182 self
.move_to((self
.x
+8)/8*8, self
.y
)
186 if c
< ' ' or c
> '~':
187 self
.msg('ignored control char: %s', `c`
)
189 if self
.x
>= self
.width
:
190 self
.move_to(0, self
.y
+ 1)
191 if self
.y
>= self
.height
:
193 self
.move_to(self
.x
, self
.height
- 1)
194 self
.lines
[self
.y
][self
.x
] = c
196 self
.attrs
[self
.y
][self
.x
] = max(self
.curattrs
)
198 self
.attrs
[self
.y
][self
.x
] = 0
201 def move_to(self
, x
, y
):
202 self
.x
= min(max(0, x
), self
.width
)
203 self
.y
= min(max(0, y
), self
.height
)
205 def move_by(self
, dx
, dy
):
206 self
.move_to(self
.x
+ dx
, self
.y
+ dy
)
208 def scroll_up(self
, nlines
):
209 del self
.lines
[:max(0, nlines
)]
210 del self
.attrs
[:max(0, nlines
)]
213 def scroll_down(self
, nlines
):
214 del self
.lines
[-max(0, nlines
):]
215 del self
.attrs
[-max(0, nlines
):]
218 def erase_left(self
):
219 x
= min(self
.width
-1, x
)
220 y
= min(self
.height
-1, y
)
221 self
.lines
[y
][:x
] = self
.blankline
[:x
]
222 self
.attrs
[y
][:x
] = self
.blankattr
[:x
]
224 def erase_right(self
):
225 x
= min(self
.width
-1, x
)
226 y
= min(self
.height
-1, y
)
227 self
.lines
[y
][x
:] = self
.blankline
[x
:]
228 self
.attrs
[y
][x
:] = self
.blankattr
[x
:]
230 def erase_line(self
):
231 self
.lines
[y
][:] = self
.blankline
232 self
.attrs
[y
][:] = self
.blankattr
234 # The following routines help automating the recognition of
235 # standard screens. A standard screen is characterized by
236 # a number of fields. A field is part of a line,
237 # characterized by a (lineno, begin, end) tuple;
238 # e.g. the first 10 characters of the second line are
239 # specified by the tuple (1, 0, 10). Fields can be:
240 # - regex: desired contents given by a regular expression,
241 # - extract: can be extracted,
242 # - cursor: screen is only valid if cursor in field,
243 # - copy: identical to another screen (position is ignored).
244 # A screen is defined as a dictionary full of fields. Screens
245 # also have names and are placed in a dictionary.
247 def reset_recognizer(self
):
250 def define_screen(self
, screenname
, fields
):
252 # Check if the fields make sense
253 for fieldname
in fields
.keys():
254 field
= fields
[fieldname
]
255 ftype
, lineno
, begin
, end
, extra
= field
256 if ftype
in ('match', 'search'):
257 extra
= regex
.compile(extra
)
258 elif ftype
== 'extract':
260 elif ftype
== 'cursor':
262 elif ftype
== 'copy':
263 if not self
.screens
.has_key(extra
):
264 raise ValueError, 'bad copy ref'
266 raise ValueError, 'bad ftype: %s' % `ftype`
267 fieldscopy
[fieldname
] = (
268 ftype
, lineno
, begin
, end
, extra
)
269 self
.screens
[screenname
] = fieldscopy
271 def which_screens(self
):
275 for name
in self
.screens
.keys():
276 ok
= self
.match_screen(name
)
279 def match_screen(self
, name
):
280 if name
in self
.busy
: raise RuntimeError, 'recursive match'
281 if name
in self
.okay
: return 1
282 if name
in self
.fail
: return 0
283 self
.busy
.append(name
)
284 fields
= self
.screens
[name
]
286 for key
in fields
.keys():
288 ftype
, lineno
, begin
, end
, extra
= field
290 if not self
.match_screen(extra
): break
291 elif ftype
== 'search':
292 text
= self
.lines
[lineno
][begin
:end
].tostring()
293 if extra
.search(text
) < 0:
295 elif ftype
== 'match':
296 text
= self
.lines
[lineno
][begin
:end
].tostring()
297 if extra
.match(text
) < 0:
299 elif ftype
== 'cursor':
300 if self
.x
!= lineno
or not \
301 begin
<= self
.y
< end
:
306 self
.okay
.append(name
)
308 self
.fail
.append(name
)
309 self
.busy
.remove(name
)
312 def extract_field(self
, screenname
, fieldname
):
313 ftype
, lineno
, begin
, end
, extra
= \
314 self
.screens
[screenname
][fieldname
]
315 return stripright(self
.lines
[lineno
][begin
:end
].tostring())
317 def extract_rect(self
, left
, top
, right
, bottom
):
319 for i
in range(top
, bottom
):
320 lines
.append(stripright(self
.lines
[i
][left
:right
])
325 def stripright(line
):
327 while i
> 0 and line
[i
-1] in string
.whitespace
: i
= i
-1