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