1'''This implements a virtual screen. This is used to support ANSI terminal 2emulation. The screen representation and state is implemented in this class. 3Most of the methods are inspired by ANSI screen control codes. The 4:class:`~pexpect.ANSI.ANSI` class extends this class to add parsing of ANSI 5escape codes. 6 7PEXPECT LICENSE 8 9 This license is approved by the OSI and FSF as GPL-compatible. 10 http://opensource.org/licenses/isc-license.txt 11 12 Copyright (c) 2012, Noah Spurrier <noah@noah.org> 13 PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY 14 PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE 15 COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES. 16 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 17 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 18 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 19 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 20 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 21 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 22 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 23 24''' 25 26import codecs 27import copy 28import sys 29 30import warnings 31 32warnings.warn(("pexpect.screen and pexpect.ANSI are deprecated. " 33 "We recommend using pyte to emulate a terminal screen: " 34 "https://pypi.python.org/pypi/pyte"), 35 stacklevel=2) 36 37NUL = 0 # Fill character; ignored on input. 38ENQ = 5 # Transmit answerback message. 39BEL = 7 # Ring the bell. 40BS = 8 # Move cursor left. 41HT = 9 # Move cursor to next tab stop. 42LF = 10 # Line feed. 43VT = 11 # Same as LF. 44FF = 12 # Same as LF. 45CR = 13 # Move cursor to left margin or newline. 46SO = 14 # Invoke G1 character set. 47SI = 15 # Invoke G0 character set. 48XON = 17 # Resume transmission. 49XOFF = 19 # Halt transmission. 50CAN = 24 # Cancel escape sequence. 51SUB = 26 # Same as CAN. 52ESC = 27 # Introduce a control sequence. 53DEL = 127 # Fill character; ignored on input. 54SPACE = u' ' # Space or blank character. 55 56PY3 = (sys.version_info[0] >= 3) 57if PY3: 58 unicode = str 59 60def constrain (n, min, max): 61 62 '''This returns a number, n constrained to the min and max bounds. ''' 63 64 if n < min: 65 return min 66 if n > max: 67 return max 68 return n 69 70class screen: 71 '''This object maintains the state of a virtual text screen as a 72 rectangular array. This maintains a virtual cursor position and handles 73 scrolling as characters are added. This supports most of the methods needed 74 by an ANSI text screen. Row and column indexes are 1-based (not zero-based, 75 like arrays). 76 77 Characters are represented internally using unicode. Methods that accept 78 input characters, when passed 'bytes' (which in Python 2 is equivalent to 79 'str'), convert them from the encoding specified in the 'encoding' 80 parameter to the constructor. Methods that return screen contents return 81 unicode strings, with the exception of __str__() under Python 2. Passing 82 ``encoding=None`` limits the API to only accept unicode input, so passing 83 bytes in will raise :exc:`TypeError`. 84 ''' 85 def __init__(self, r=24, c=80, encoding='latin-1', encoding_errors='replace'): 86 '''This initializes a blank screen of the given dimensions.''' 87 88 self.rows = r 89 self.cols = c 90 self.encoding = encoding 91 self.encoding_errors = encoding_errors 92 if encoding is not None: 93 self.decoder = codecs.getincrementaldecoder(encoding)(encoding_errors) 94 else: 95 self.decoder = None 96 self.cur_r = 1 97 self.cur_c = 1 98 self.cur_saved_r = 1 99 self.cur_saved_c = 1 100 self.scroll_row_start = 1 101 self.scroll_row_end = self.rows 102 self.w = [ [SPACE] * self.cols for _ in range(self.rows)] 103 104 def _decode(self, s): 105 '''This converts from the external coding system (as passed to 106 the constructor) to the internal one (unicode). ''' 107 if self.decoder is not None: 108 return self.decoder.decode(s) 109 else: 110 raise TypeError("This screen was constructed with encoding=None, " 111 "so it does not handle bytes.") 112 113 def _unicode(self): 114 '''This returns a printable representation of the screen as a unicode 115 string (which, under Python 3.x, is the same as 'str'). The end of each 116 screen line is terminated by a newline.''' 117 118 return u'\n'.join ([ u''.join(c) for c in self.w ]) 119 120 if PY3: 121 __str__ = _unicode 122 else: 123 __unicode__ = _unicode 124 125 def __str__(self): 126 '''This returns a printable representation of the screen. The end of 127 each screen line is terminated by a newline. ''' 128 encoding = self.encoding or 'ascii' 129 return self._unicode().encode(encoding, 'replace') 130 131 def dump (self): 132 '''This returns a copy of the screen as a unicode string. This is similar to 133 __str__/__unicode__ except that lines are not terminated with line 134 feeds.''' 135 136 return u''.join ([ u''.join(c) for c in self.w ]) 137 138 def pretty (self): 139 '''This returns a copy of the screen as a unicode string with an ASCII 140 text box around the screen border. This is similar to 141 __str__/__unicode__ except that it adds a box.''' 142 143 top_bot = u'+' + u'-'*self.cols + u'+\n' 144 return top_bot + u'\n'.join([u'|'+line+u'|' for line in unicode(self).split(u'\n')]) + u'\n' + top_bot 145 146 def fill (self, ch=SPACE): 147 148 if isinstance(ch, bytes): 149 ch = self._decode(ch) 150 151 self.fill_region (1,1,self.rows,self.cols, ch) 152 153 def fill_region (self, rs,cs, re,ce, ch=SPACE): 154 155 if isinstance(ch, bytes): 156 ch = self._decode(ch) 157 158 rs = constrain (rs, 1, self.rows) 159 re = constrain (re, 1, self.rows) 160 cs = constrain (cs, 1, self.cols) 161 ce = constrain (ce, 1, self.cols) 162 if rs > re: 163 rs, re = re, rs 164 if cs > ce: 165 cs, ce = ce, cs 166 for r in range (rs, re+1): 167 for c in range (cs, ce + 1): 168 self.put_abs (r,c,ch) 169 170 def cr (self): 171 '''This moves the cursor to the beginning (col 1) of the current row. 172 ''' 173 174 self.cursor_home (self.cur_r, 1) 175 176 def lf (self): 177 '''This moves the cursor down with scrolling. 178 ''' 179 180 old_r = self.cur_r 181 self.cursor_down() 182 if old_r == self.cur_r: 183 self.scroll_up () 184 self.erase_line() 185 186 def crlf (self): 187 '''This advances the cursor with CRLF properties. 188 The cursor will line wrap and the screen may scroll. 189 ''' 190 191 self.cr () 192 self.lf () 193 194 def newline (self): 195 '''This is an alias for crlf(). 196 ''' 197 198 self.crlf() 199 200 def put_abs (self, r, c, ch): 201 '''Screen array starts at 1 index.''' 202 203 r = constrain (r, 1, self.rows) 204 c = constrain (c, 1, self.cols) 205 if isinstance(ch, bytes): 206 ch = self._decode(ch)[0] 207 else: 208 ch = ch[0] 209 self.w[r-1][c-1] = ch 210 211 def put (self, ch): 212 '''This puts a characters at the current cursor position. 213 ''' 214 215 if isinstance(ch, bytes): 216 ch = self._decode(ch) 217 218 self.put_abs (self.cur_r, self.cur_c, ch) 219 220 def insert_abs (self, r, c, ch): 221 '''This inserts a character at (r,c). Everything under 222 and to the right is shifted right one character. 223 The last character of the line is lost. 224 ''' 225 226 if isinstance(ch, bytes): 227 ch = self._decode(ch) 228 229 r = constrain (r, 1, self.rows) 230 c = constrain (c, 1, self.cols) 231 for ci in range (self.cols, c, -1): 232 self.put_abs (r,ci, self.get_abs(r,ci-1)) 233 self.put_abs (r,c,ch) 234 235 def insert (self, ch): 236 237 if isinstance(ch, bytes): 238 ch = self._decode(ch) 239 240 self.insert_abs (self.cur_r, self.cur_c, ch) 241 242 def get_abs (self, r, c): 243 244 r = constrain (r, 1, self.rows) 245 c = constrain (c, 1, self.cols) 246 return self.w[r-1][c-1] 247 248 def get (self): 249 250 self.get_abs (self.cur_r, self.cur_c) 251 252 def get_region (self, rs,cs, re,ce): 253 '''This returns a list of lines representing the region. 254 ''' 255 256 rs = constrain (rs, 1, self.rows) 257 re = constrain (re, 1, self.rows) 258 cs = constrain (cs, 1, self.cols) 259 ce = constrain (ce, 1, self.cols) 260 if rs > re: 261 rs, re = re, rs 262 if cs > ce: 263 cs, ce = ce, cs 264 sc = [] 265 for r in range (rs, re+1): 266 line = u'' 267 for c in range (cs, ce + 1): 268 ch = self.get_abs (r,c) 269 line = line + ch 270 sc.append (line) 271 return sc 272 273 def cursor_constrain (self): 274 '''This keeps the cursor within the screen area. 275 ''' 276 277 self.cur_r = constrain (self.cur_r, 1, self.rows) 278 self.cur_c = constrain (self.cur_c, 1, self.cols) 279 280 def cursor_home (self, r=1, c=1): # <ESC>[{ROW};{COLUMN}H 281 282 self.cur_r = r 283 self.cur_c = c 284 self.cursor_constrain () 285 286 def cursor_back (self,count=1): # <ESC>[{COUNT}D (not confused with down) 287 288 self.cur_c = self.cur_c - count 289 self.cursor_constrain () 290 291 def cursor_down (self,count=1): # <ESC>[{COUNT}B (not confused with back) 292 293 self.cur_r = self.cur_r + count 294 self.cursor_constrain () 295 296 def cursor_forward (self,count=1): # <ESC>[{COUNT}C 297 298 self.cur_c = self.cur_c + count 299 self.cursor_constrain () 300 301 def cursor_up (self,count=1): # <ESC>[{COUNT}A 302 303 self.cur_r = self.cur_r - count 304 self.cursor_constrain () 305 306 def cursor_up_reverse (self): # <ESC> M (called RI -- Reverse Index) 307 308 old_r = self.cur_r 309 self.cursor_up() 310 if old_r == self.cur_r: 311 self.scroll_up() 312 313 def cursor_force_position (self, r, c): # <ESC>[{ROW};{COLUMN}f 314 '''Identical to Cursor Home.''' 315 316 self.cursor_home (r, c) 317 318 def cursor_save (self): # <ESC>[s 319 '''Save current cursor position.''' 320 321 self.cursor_save_attrs() 322 323 def cursor_unsave (self): # <ESC>[u 324 '''Restores cursor position after a Save Cursor.''' 325 326 self.cursor_restore_attrs() 327 328 def cursor_save_attrs (self): # <ESC>7 329 '''Save current cursor position.''' 330 331 self.cur_saved_r = self.cur_r 332 self.cur_saved_c = self.cur_c 333 334 def cursor_restore_attrs (self): # <ESC>8 335 '''Restores cursor position after a Save Cursor.''' 336 337 self.cursor_home (self.cur_saved_r, self.cur_saved_c) 338 339 def scroll_constrain (self): 340 '''This keeps the scroll region within the screen region.''' 341 342 if self.scroll_row_start <= 0: 343 self.scroll_row_start = 1 344 if self.scroll_row_end > self.rows: 345 self.scroll_row_end = self.rows 346 347 def scroll_screen (self): # <ESC>[r 348 '''Enable scrolling for entire display.''' 349 350 self.scroll_row_start = 1 351 self.scroll_row_end = self.rows 352 353 def scroll_screen_rows (self, rs, re): # <ESC>[{start};{end}r 354 '''Enable scrolling from row {start} to row {end}.''' 355 356 self.scroll_row_start = rs 357 self.scroll_row_end = re 358 self.scroll_constrain() 359 360 def scroll_down (self): # <ESC>D 361 '''Scroll display down one line.''' 362 363 # Screen is indexed from 1, but arrays are indexed from 0. 364 s = self.scroll_row_start - 1 365 e = self.scroll_row_end - 1 366 self.w[s+1:e+1] = copy.deepcopy(self.w[s:e]) 367 368 def scroll_up (self): # <ESC>M 369 '''Scroll display up one line.''' 370 371 # Screen is indexed from 1, but arrays are indexed from 0. 372 s = self.scroll_row_start - 1 373 e = self.scroll_row_end - 1 374 self.w[s:e] = copy.deepcopy(self.w[s+1:e+1]) 375 376 def erase_end_of_line (self): # <ESC>[0K -or- <ESC>[K 377 '''Erases from the current cursor position to the end of the current 378 line.''' 379 380 self.fill_region (self.cur_r, self.cur_c, self.cur_r, self.cols) 381 382 def erase_start_of_line (self): # <ESC>[1K 383 '''Erases from the current cursor position to the start of the current 384 line.''' 385 386 self.fill_region (self.cur_r, 1, self.cur_r, self.cur_c) 387 388 def erase_line (self): # <ESC>[2K 389 '''Erases the entire current line.''' 390 391 self.fill_region (self.cur_r, 1, self.cur_r, self.cols) 392 393 def erase_down (self): # <ESC>[0J -or- <ESC>[J 394 '''Erases the screen from the current line down to the bottom of the 395 screen.''' 396 397 self.erase_end_of_line () 398 self.fill_region (self.cur_r + 1, 1, self.rows, self.cols) 399 400 def erase_up (self): # <ESC>[1J 401 '''Erases the screen from the current line up to the top of the 402 screen.''' 403 404 self.erase_start_of_line () 405 self.fill_region (self.cur_r-1, 1, 1, self.cols) 406 407 def erase_screen (self): # <ESC>[2J 408 '''Erases the screen with the background color.''' 409 410 self.fill () 411 412 def set_tab (self): # <ESC>H 413 '''Sets a tab at the current position.''' 414 415 pass 416 417 def clear_tab (self): # <ESC>[g 418 '''Clears tab at the current position.''' 419 420 pass 421 422 def clear_all_tabs (self): # <ESC>[3g 423 '''Clears all tabs.''' 424 425 pass 426 427# Insert line Esc [ Pn L 428# Delete line Esc [ Pn M 429# Delete character Esc [ Pn P 430# Scrolling region Esc [ Pn(top);Pn(bot) r 431 432