OLD | NEW |
(Empty) | |
| 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 |
OLD | NEW |