1 """This implements a virtual screen. This is used to support ANSI terminal
2 emulation. The screen representation and state is implemented in this class.
3 Most of the methods are inspired by ANSI screen control codes. The ANSI class
4 extends this class to add parsing of ANSI escape codes.
6 $Id: screen.py 486 2007-07-13 01:04:16Z noah $
11 NUL = 0 # Fill character; ignored on input.
12 ENQ = 5 # Transmit answerback message.
13 BEL = 7 # Ring the bell.
14 BS = 8 # Move cursor left.
15 HT = 9 # Move cursor to next tab stop.
19 CR = 13 # Move cursor to left margin or newline.
20 SO = 14 # Invoke G1 character set.
21 SI = 15 # Invoke G0 character set.
22 XON = 17 # Resume transmission.
23 XOFF = 19 # Halt transmission.
24 CAN = 24 # Cancel escape sequence.
25 SUB = 26 # Same as CAN.
26 ESC = 27 # Introduce a control sequence.
27 DEL = 127 # Fill character; ignored on input.
28 SPACE = chr(32) # Space or blank character.
30 def constrain (n, min, max):
32 """This returns a number, n constrained to the min and max bounds. """
42 """This object maintains the state of a virtual text screen as a
43 rectangluar array. This maintains a virtual cursor position and handles
44 scrolling as characters are added. This supports most of the methods needed
45 by an ANSI text screen. Row and column indexes are 1-based (not zero-based,
48 def __init__ (self, r=24,c=80):
50 """This initializes a blank scree of the given dimentions."""
58 self.scroll_row_start = 1
59 self.scroll_row_end = self.rows
60 self.w = [ [SPACE] * self.cols for c in range(self.rows)]
64 """This returns a printable representation of the screen. The end of
65 each screen line is terminated by a newline. """
67 return '\n'.join ([ ''.join(c) for c in self.w ])
71 """This returns a copy of the screen as a string. This is similar to
72 __str__ except that lines are not terminated with line feeds. """
74 return ''.join ([ ''.join(c) for c in self.w ])
78 """This returns a copy of the screen as a string with an ASCII text box
79 around the screen border. This is similar to __str__ except that it
82 top_bot = '+' + '-'*self.cols + '+\n'
83 return top_bot + '\n'.join(['|'+line+'|' for line in str(self).split('\n')]) + '\n' + top_bot
85 def fill (self, ch=SPACE):
87 self.fill_region (1,1,self.rows,self.cols, ch)
89 def fill_region (self, rs,cs, re,ce, ch=SPACE):
91 rs = constrain (rs, 1, self.rows)
92 re = constrain (re, 1, self.rows)
93 cs = constrain (cs, 1, self.cols)
94 ce = constrain (ce, 1, self.cols)
99 for r in range (rs, re+1):
100 for c in range (cs, ce + 1):
101 self.put_abs (r,c,ch)
105 """This moves the cursor to the beginning (col 1) of the current row.
108 self.cursor_home (self.cur_r, 1)
112 """This moves the cursor down with scrolling.
117 if old_r == self.cur_r:
123 """This advances the cursor with CRLF properties.
124 The cursor will line wrap and the screen may scroll.
132 """This is an alias for crlf().
137 def put_abs (self, r, c, ch):
139 """Screen array starts at 1 index."""
141 r = constrain (r, 1, self.rows)
142 c = constrain (c, 1, self.cols)
144 self.w[r-1][c-1] = ch
148 """This puts a characters at the current cursor position.
151 self.put_abs (self.cur_r, self.cur_c, ch)
153 def insert_abs (self, r, c, ch):
155 """This inserts a character at (r,c). Everything under
156 and to the right is shifted right one character.
157 The last character of the line is lost.
160 r = constrain (r, 1, self.rows)
161 c = constrain (c, 1, self.cols)
162 for ci in range (self.cols, c, -1):
163 self.put_abs (r,ci, self.get_abs(r,ci-1))
164 self.put_abs (r,c,ch)
166 def insert (self, ch):
168 self.insert_abs (self.cur_r, self.cur_c, ch)
170 def get_abs (self, r, c):
172 r = constrain (r, 1, self.rows)
173 c = constrain (c, 1, self.cols)
174 return self.w[r-1][c-1]
178 self.get_abs (self.cur_r, self.cur_c)
180 def get_region (self, rs,cs, re,ce):
182 """This returns a list of lines representing the region.
185 rs = constrain (rs, 1, self.rows)
186 re = constrain (re, 1, self.rows)
187 cs = constrain (cs, 1, self.cols)
188 ce = constrain (ce, 1, self.cols)
194 for r in range (rs, re+1):
196 for c in range (cs, ce + 1):
197 ch = self.get_abs (r,c)
202 def cursor_constrain (self):
204 """This keeps the cursor within the screen area.
207 self.cur_r = constrain (self.cur_r, 1, self.rows)
208 self.cur_c = constrain (self.cur_c, 1, self.cols)
210 def cursor_home (self, r=1, c=1): # <ESC>[{ROW};{COLUMN}H
214 self.cursor_constrain ()
216 def cursor_back (self,count=1): # <ESC>[{COUNT}D (not confused with down)
218 self.cur_c = self.cur_c - count
219 self.cursor_constrain ()
221 def cursor_down (self,count=1): # <ESC>[{COUNT}B (not confused with back)
223 self.cur_r = self.cur_r + count
224 self.cursor_constrain ()
226 def cursor_forward (self,count=1): # <ESC>[{COUNT}C
228 self.cur_c = self.cur_c + count
229 self.cursor_constrain ()
231 def cursor_up (self,count=1): # <ESC>[{COUNT}A
233 self.cur_r = self.cur_r - count
234 self.cursor_constrain ()
236 def cursor_up_reverse (self): # <ESC> M (called RI -- Reverse Index)
240 if old_r == self.cur_r:
243 def cursor_force_position (self, r, c): # <ESC>[{ROW};{COLUMN}f
245 """Identical to Cursor Home."""
247 self.cursor_home (r, c)
249 def cursor_save (self): # <ESC>[s
251 """Save current cursor position."""
253 self.cursor_save_attrs()
255 def cursor_unsave (self): # <ESC>[u
257 """Restores cursor position after a Save Cursor."""
259 self.cursor_restore_attrs()
261 def cursor_save_attrs (self): # <ESC>7
263 """Save current cursor position."""
265 self.cur_saved_r = self.cur_r
266 self.cur_saved_c = self.cur_c
268 def cursor_restore_attrs (self): # <ESC>8
270 """Restores cursor position after a Save Cursor."""
272 self.cursor_home (self.cur_saved_r, self.cur_saved_c)
274 def scroll_constrain (self):
276 """This keeps the scroll region within the screen region."""
278 if self.scroll_row_start <= 0:
279 self.scroll_row_start = 1
280 if self.scroll_row_end > self.rows:
281 self.scroll_row_end = self.rows
283 def scroll_screen (self): # <ESC>[r
285 """Enable scrolling for entire display."""
287 self.scroll_row_start = 1
288 self.scroll_row_end = self.rows
290 def scroll_screen_rows (self, rs, re): # <ESC>[{start};{end}r
292 """Enable scrolling from row {start} to row {end}."""
294 self.scroll_row_start = rs
295 self.scroll_row_end = re
296 self.scroll_constrain()
298 def scroll_down (self): # <ESC>D
300 """Scroll display down one line."""
302 # Screen is indexed from 1, but arrays are indexed from 0.
303 s = self.scroll_row_start - 1
304 e = self.scroll_row_end - 1
305 self.w[s+1:e+1] = copy.deepcopy(self.w[s:e])
307 def scroll_up (self): # <ESC>M
309 """Scroll display up one line."""
311 # Screen is indexed from 1, but arrays are indexed from 0.
312 s = self.scroll_row_start - 1
313 e = self.scroll_row_end - 1
314 self.w[s:e] = copy.deepcopy(self.w[s+1:e+1])
316 def erase_end_of_line (self): # <ESC>[0K -or- <ESC>[K
318 """Erases from the current cursor position to the end of the current
321 self.fill_region (self.cur_r, self.cur_c, self.cur_r, self.cols)
323 def erase_start_of_line (self): # <ESC>[1K
325 """Erases from the current cursor position to the start of the current
328 self.fill_region (self.cur_r, 1, self.cur_r, self.cur_c)
330 def erase_line (self): # <ESC>[2K
332 """Erases the entire current line."""
334 self.fill_region (self.cur_r, 1, self.cur_r, self.cols)
336 def erase_down (self): # <ESC>[0J -or- <ESC>[J
338 """Erases the screen from the current line down to the bottom of the
341 self.erase_end_of_line ()
342 self.fill_region (self.cur_r + 1, 1, self.rows, self.cols)
344 def erase_up (self): # <ESC>[1J
346 """Erases the screen from the current line up to the top of the
349 self.erase_start_of_line ()
350 self.fill_region (self.cur_r-1, 1, 1, self.cols)
352 def erase_screen (self): # <ESC>[2J
354 """Erases the screen with the background color."""
358 def set_tab (self): # <ESC>H
360 """Sets a tab at the current position."""
364 def clear_tab (self): # <ESC>[g
366 """Clears tab at the current position."""
370 def clear_all_tabs (self): # <ESC>[3g
372 """Clears all tabs."""
376 # Insert line Esc [ Pn L
377 # Delete line Esc [ Pn M
378 # Delete character Esc [ Pn P
379 # Scrolling region Esc [ Pn(top);Pn(bot) r