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. 5 6 $Id: screen.py 486 2007-07-13 01:04:16Z noah $ 7 """ 8 9 import copy 10 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. 16 LF = 10 # Line feed. 17 VT = 11 # Same as LF. 18 FF = 12 # Same as LF. 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. 29 30 def constrain (n, min, max): 31 32 """This returns a number, n constrained to the min and max bounds. """ 33 34 if n < min: 35 return min 36 if n > max: 37 return max 38 return n 39 40 class screen: 41 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, 46 like arrays). """ 47 48 def __init__ (self, r=24,c=80): 49 50 """This initializes a blank scree of the given dimentions.""" 51 52 self.rows = r 53 self.cols = c 54 self.cur_r = 1 55 self.cur_c = 1 56 self.cur_saved_r = 1 57 self.cur_saved_c = 1 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)] 61 62 def __str__ (self): 63 64 """This returns a printable representation of the screen. The end of 65 each screen line is terminated by a newline. """ 66 67 return '\n'.join ([ ''.join(c) for c in self.w ]) 68 69 def dump (self): 70 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. """ 73 74 return ''.join ([ ''.join(c) for c in self.w ]) 75 76 def pretty (self): 77 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 80 adds a box. """ 81 82 top_bot = '+' + '-'*self.cols + '+\n' 83 return top_bot + '\n'.join(['|'+line+'|' for line in str(self).split('\n')]) + '\n' + top_bot 84 85 def fill (self, ch=SPACE): 86 87 self.fill_region (1,1,self.rows,self.cols, ch) 88 89 def fill_region (self, rs,cs, re,ce, ch=SPACE): 90 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) 95 if rs > re: 96 rs, re = re, rs 97 if cs > ce: 98 cs, ce = ce, cs 99 for r in range (rs, re+1): 100 for c in range (cs, ce + 1): 101 self.put_abs (r,c,ch) 102 103 def cr (self): 104 105 """This moves the cursor to the beginning (col 1) of the current row. 106 """ 107 108 self.cursor_home (self.cur_r, 1) 109 110 def lf (self): 111 112 """This moves the cursor down with scrolling. 113 """ 114 115 old_r = self.cur_r 116 self.cursor_down() 117 if old_r == self.cur_r: 118 self.scroll_up () 119 self.erase_line() 120 121 def crlf (self): 122 123 """This advances the cursor with CRLF properties. 124 The cursor will line wrap and the screen may scroll. 125 """ 126 127 self.cr () 128 self.lf () 129 130 def newline (self): 131 132 """This is an alias for crlf(). 133 """ 134 135 self.crlf() 136 137 def put_abs (self, r, c, ch): 138 139 """Screen array starts at 1 index.""" 140 141 r = constrain (r, 1, self.rows) 142 c = constrain (c, 1, self.cols) 143 ch = str(ch)[0] 144 self.w[r-1][c-1] = ch 145 146 def put (self, ch): 147 148 """This puts a characters at the current cursor position. 149 """ 150 151 self.put_abs (self.cur_r, self.cur_c, ch) 152 153 def insert_abs (self, r, c, ch): 154 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. 158 """ 159 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) 165 166 def insert (self, ch): 167 168 self.insert_abs (self.cur_r, self.cur_c, ch) 169 170 def get_abs (self, r, c): 171 172 r = constrain (r, 1, self.rows) 173 c = constrain (c, 1, self.cols) 174 return self.w[r-1][c-1] 175 176 def get (self): 177 178 self.get_abs (self.cur_r, self.cur_c) 179 180 def get_region (self, rs,cs, re,ce): 181 182 """This returns a list of lines representing the region. 183 """ 184 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) 189 if rs > re: 190 rs, re = re, rs 191 if cs > ce: 192 cs, ce = ce, cs 193 sc = [] 194 for r in range (rs, re+1): 195 line = '' 196 for c in range (cs, ce + 1): 197 ch = self.get_abs (r,c) 198 line = line + ch 199 sc.append (line) 200 return sc 201 202 def cursor_constrain (self): 203 204 """This keeps the cursor within the screen area. 205 """ 206 207 self.cur_r = constrain (self.cur_r, 1, self.rows) 208 self.cur_c = constrain (self.cur_c, 1, self.cols) 209 210 def cursor_home (self, r=1, c=1): # <ESC>[{ROW};{COLUMN}H 211 212 self.cur_r = r 213 self.cur_c = c 214 self.cursor_constrain () 215 216 def cursor_back (self,count=1): # <ESC>[{COUNT}D (not confused with down) 217 218 self.cur_c = self.cur_c - count 219 self.cursor_constrain () 220 221 def cursor_down (self,count=1): # <ESC>[{COUNT}B (not confused with back) 222 223 self.cur_r = self.cur_r + count 224 self.cursor_constrain () 225 226 def cursor_forward (self,count=1): # <ESC>[{COUNT}C 227 228 self.cur_c = self.cur_c + count 229 self.cursor_constrain () 230 231 def cursor_up (self,count=1): # <ESC>[{COUNT}A 232 233 self.cur_r = self.cur_r - count 234 self.cursor_constrain () 235 236 def cursor_up_reverse (self): # <ESC> M (called RI -- Reverse Index) 237 238 old_r = self.cur_r 239 self.cursor_up() 240 if old_r == self.cur_r: 241 self.scroll_up() 242 243 def cursor_force_position (self, r, c): # <ESC>[{ROW};{COLUMN}f 244 245 """Identical to Cursor Home.""" 246 247 self.cursor_home (r, c) 248 249 def cursor_save (self): # <ESC>[s 250 251 """Save current cursor position.""" 252 253 self.cursor_save_attrs() 254 255 def cursor_unsave (self): # <ESC>[u 256 257 """Restores cursor position after a Save Cursor.""" 258 259 self.cursor_restore_attrs() 260 261 def cursor_save_attrs (self): # <ESC>7 262 263 """Save current cursor position.""" 264 265 self.cur_saved_r = self.cur_r 266 self.cur_saved_c = self.cur_c 267 268 def cursor_restore_attrs (self): # <ESC>8 269 270 """Restores cursor position after a Save Cursor.""" 271 272 self.cursor_home (self.cur_saved_r, self.cur_saved_c) 273 274 def scroll_constrain (self): 275 276 """This keeps the scroll region within the screen region.""" 277 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 282 283 def scroll_screen (self): # <ESC>[r 284 285 """Enable scrolling for entire display.""" 286 287 self.scroll_row_start = 1 288 self.scroll_row_end = self.rows 289 290 def scroll_screen_rows (self, rs, re): # <ESC>[{start};{end}r 291 292 """Enable scrolling from row {start} to row {end}.""" 293 294 self.scroll_row_start = rs 295 self.scroll_row_end = re 296 self.scroll_constrain() 297 298 def scroll_down (self): # <ESC>D 299 300 """Scroll display down one line.""" 301 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]) 306 307 def scroll_up (self): # <ESC>M 308 309 """Scroll display up one line.""" 310 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]) 315 316 def erase_end_of_line (self): # <ESC>[0K -or- <ESC>[K 317 318 """Erases from the current cursor position to the end of the current 319 line.""" 320 321 self.fill_region (self.cur_r, self.cur_c, self.cur_r, self.cols) 322 323 def erase_start_of_line (self): # <ESC>[1K 324 325 """Erases from the current cursor position to the start of the current 326 line.""" 327 328 self.fill_region (self.cur_r, 1, self.cur_r, self.cur_c) 329 330 def erase_line (self): # <ESC>[2K 331 332 """Erases the entire current line.""" 333 334 self.fill_region (self.cur_r, 1, self.cur_r, self.cols) 335 336 def erase_down (self): # <ESC>[0J -or- <ESC>[J 337 338 """Erases the screen from the current line down to the bottom of the 339 screen.""" 340 341 self.erase_end_of_line () 342 self.fill_region (self.cur_r + 1, 1, self.rows, self.cols) 343 344 def erase_up (self): # <ESC>[1J 345 346 """Erases the screen from the current line up to the top of the 347 screen.""" 348 349 self.erase_start_of_line () 350 self.fill_region (self.cur_r-1, 1, 1, self.cols) 351 352 def erase_screen (self): # <ESC>[2J 353 354 """Erases the screen with the background color.""" 355 356 self.fill () 357 358 def set_tab (self): # <ESC>H 359 360 """Sets a tab at the current position.""" 361 362 pass 363 364 def clear_tab (self): # <ESC>[g 365 366 """Clears tab at the current position.""" 367 368 pass 369 370 def clear_all_tabs (self): # <ESC>[3g 371 372 """Clears all tabs.""" 373 374 pass 375 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 380 381