Home | History | Annotate | Download | only in pexpect-2.4
      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