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