Home | History | Annotate | Download | only in pexpect
      1 """This implements an ANSI (VT100) terminal emulator as a subclass of screen.
      2 
      3 PEXPECT LICENSE
      4 
      5     This license is approved by the OSI and FSF as GPL-compatible.
      6         http://opensource.org/licenses/isc-license.txt
      7 
      8     Copyright (c) 2012, Noah Spurrier <noah (at] noah.org>
      9     PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY
     10     PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE
     11     COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES.
     12     THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     13     WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     14     MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     15     ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     16     WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     17     ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     18     OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     19 
     20 """
     21 
     22 # references:
     23 #     http://en.wikipedia.org/wiki/ANSI_escape_code
     24 #     http://www.retards.org/terminals/vt102.html
     25 #     http://vt100.net/docs/vt102-ug/contents.html
     26 #     http://vt100.net/docs/vt220-rm/
     27 #     http://www.termsys.demon.co.uk/vtansi.htm
     28 
     29 import screen
     30 import FSM
     31 import copy
     32 import string
     33 
     34 #
     35 # The 'Do.*' functions are helper functions for the ANSI class.
     36 #
     37 def DoEmit (fsm):
     38 
     39     screen = fsm.memory[0]
     40     screen.write_ch(fsm.input_symbol)
     41 
     42 def DoStartNumber (fsm):
     43 
     44     fsm.memory.append (fsm.input_symbol)
     45 
     46 def DoBuildNumber (fsm):
     47 
     48     ns = fsm.memory.pop()
     49     ns = ns + fsm.input_symbol
     50     fsm.memory.append (ns)
     51 
     52 def DoBackOne (fsm):
     53 
     54     screen = fsm.memory[0]
     55     screen.cursor_back ()
     56 
     57 def DoBack (fsm):
     58 
     59     count = int(fsm.memory.pop())
     60     screen = fsm.memory[0]
     61     screen.cursor_back (count)
     62 
     63 def DoDownOne (fsm):
     64 
     65     screen = fsm.memory[0]
     66     screen.cursor_down ()
     67 
     68 def DoDown (fsm):
     69 
     70     count = int(fsm.memory.pop())
     71     screen = fsm.memory[0]
     72     screen.cursor_down (count)
     73 
     74 def DoForwardOne (fsm):
     75 
     76     screen = fsm.memory[0]
     77     screen.cursor_forward ()
     78 
     79 def DoForward (fsm):
     80 
     81     count = int(fsm.memory.pop())
     82     screen = fsm.memory[0]
     83     screen.cursor_forward (count)
     84 
     85 def DoUpReverse (fsm):
     86 
     87     screen = fsm.memory[0]
     88     screen.cursor_up_reverse()
     89 
     90 def DoUpOne (fsm):
     91 
     92     screen = fsm.memory[0]
     93     screen.cursor_up ()
     94 
     95 def DoUp (fsm):
     96 
     97     count = int(fsm.memory.pop())
     98     screen = fsm.memory[0]
     99     screen.cursor_up (count)
    100 
    101 def DoHome (fsm):
    102 
    103     c = int(fsm.memory.pop())
    104     r = int(fsm.memory.pop())
    105     screen = fsm.memory[0]
    106     screen.cursor_home (r,c)
    107 
    108 def DoHomeOrigin (fsm):
    109 
    110     c = 1
    111     r = 1
    112     screen = fsm.memory[0]
    113     screen.cursor_home (r,c)
    114 
    115 def DoEraseDown (fsm):
    116 
    117     screen = fsm.memory[0]
    118     screen.erase_down()
    119 
    120 def DoErase (fsm):
    121 
    122     arg = int(fsm.memory.pop())
    123     screen = fsm.memory[0]
    124     if arg == 0:
    125         screen.erase_down()
    126     elif arg == 1:
    127         screen.erase_up()
    128     elif arg == 2:
    129         screen.erase_screen()
    130 
    131 def DoEraseEndOfLine (fsm):
    132 
    133     screen = fsm.memory[0]
    134     screen.erase_end_of_line()
    135 
    136 def DoEraseLine (fsm):
    137 
    138     arg = int(fsm.memory.pop())
    139     screen = fsm.memory[0]
    140     if arg == 0:
    141         screen.erase_end_of_line()
    142     elif arg == 1:
    143         screen.erase_start_of_line()
    144     elif arg == 2:
    145         screen.erase_line()
    146 
    147 def DoEnableScroll (fsm):
    148 
    149     screen = fsm.memory[0]
    150     screen.scroll_screen()
    151 
    152 def DoCursorSave (fsm):
    153 
    154     screen = fsm.memory[0]
    155     screen.cursor_save_attrs()
    156 
    157 def DoCursorRestore (fsm):
    158 
    159     screen = fsm.memory[0]
    160     screen.cursor_restore_attrs()
    161 
    162 def DoScrollRegion (fsm):
    163 
    164     screen = fsm.memory[0]
    165     r2 = int(fsm.memory.pop())
    166     r1 = int(fsm.memory.pop())
    167     screen.scroll_screen_rows (r1,r2)
    168 
    169 def DoMode (fsm):
    170 
    171     screen = fsm.memory[0]
    172     mode = fsm.memory.pop() # Should be 4
    173     # screen.setReplaceMode ()
    174 
    175 def DoLog (fsm):
    176 
    177     screen = fsm.memory[0]
    178     fsm.memory = [screen]
    179     fout = open ('log', 'a')
    180     fout.write (fsm.input_symbol + ',' + fsm.current_state + '\n')
    181     fout.close()
    182 
    183 class term (screen.screen):
    184 
    185     """This class is an abstract, generic terminal.
    186     This does nothing. This is a placeholder that
    187     provides a common base class for other terminals
    188     such as an ANSI terminal. """
    189 
    190     def __init__ (self, r=24, c=80):
    191 
    192         screen.screen.__init__(self, r,c)
    193 
    194 class ANSI (term):
    195 
    196     """This class implements an ANSI (VT100) terminal.
    197     It is a stream filter that recognizes ANSI terminal
    198     escape sequences and maintains the state of a screen object. """
    199 
    200     def __init__ (self, r=24,c=80):
    201 
    202         term.__init__(self,r,c)
    203 
    204         #self.screen = screen (24,80)
    205         self.state = FSM.FSM ('INIT',[self])
    206         self.state.set_default_transition (DoLog, 'INIT')
    207         self.state.add_transition_any ('INIT', DoEmit, 'INIT')
    208         self.state.add_transition ('\x1b', 'INIT', None, 'ESC')
    209         self.state.add_transition_any ('ESC', DoLog, 'INIT')
    210         self.state.add_transition ('(', 'ESC', None, 'G0SCS')
    211         self.state.add_transition (')', 'ESC', None, 'G1SCS')
    212         self.state.add_transition_list ('AB012', 'G0SCS', None, 'INIT')
    213         self.state.add_transition_list ('AB012', 'G1SCS', None, 'INIT')
    214         self.state.add_transition ('7', 'ESC', DoCursorSave, 'INIT')
    215         self.state.add_transition ('8', 'ESC', DoCursorRestore, 'INIT')
    216         self.state.add_transition ('M', 'ESC', DoUpReverse, 'INIT')
    217         self.state.add_transition ('>', 'ESC', DoUpReverse, 'INIT')
    218         self.state.add_transition ('<', 'ESC', DoUpReverse, 'INIT')
    219         self.state.add_transition ('=', 'ESC', None, 'INIT') # Selects application keypad.
    220         self.state.add_transition ('#', 'ESC', None, 'GRAPHICS_POUND')
    221         self.state.add_transition_any ('GRAPHICS_POUND', None, 'INIT')
    222         self.state.add_transition ('[', 'ESC', None, 'ELB')
    223         # ELB means Escape Left Bracket. That is ^[[
    224         self.state.add_transition ('H', 'ELB', DoHomeOrigin, 'INIT')
    225         self.state.add_transition ('D', 'ELB', DoBackOne, 'INIT')
    226         self.state.add_transition ('B', 'ELB', DoDownOne, 'INIT')
    227         self.state.add_transition ('C', 'ELB', DoForwardOne, 'INIT')
    228         self.state.add_transition ('A', 'ELB', DoUpOne, 'INIT')
    229         self.state.add_transition ('J', 'ELB', DoEraseDown, 'INIT')
    230         self.state.add_transition ('K', 'ELB', DoEraseEndOfLine, 'INIT')
    231         self.state.add_transition ('r', 'ELB', DoEnableScroll, 'INIT')
    232         self.state.add_transition ('m', 'ELB', None, 'INIT')
    233         self.state.add_transition ('?', 'ELB', None, 'MODECRAP')
    234         self.state.add_transition_list (string.digits, 'ELB', DoStartNumber, 'NUMBER_1')
    235         self.state.add_transition_list (string.digits, 'NUMBER_1', DoBuildNumber, 'NUMBER_1')
    236         self.state.add_transition ('D', 'NUMBER_1', DoBack, 'INIT')
    237         self.state.add_transition ('B', 'NUMBER_1', DoDown, 'INIT')
    238         self.state.add_transition ('C', 'NUMBER_1', DoForward, 'INIT')
    239         self.state.add_transition ('A', 'NUMBER_1', DoUp, 'INIT')
    240         self.state.add_transition ('J', 'NUMBER_1', DoErase, 'INIT')
    241         self.state.add_transition ('K', 'NUMBER_1', DoEraseLine, 'INIT')
    242         self.state.add_transition ('l', 'NUMBER_1', DoMode, 'INIT')
    243         ### It gets worse... the 'm' code can have infinite number of
    244         ### number;number;number before it. I've never seen more than two,
    245         ### but the specs say it's allowed. crap!
    246         self.state.add_transition ('m', 'NUMBER_1', None, 'INIT')
    247         ### LED control. Same implementation problem as 'm' code.
    248         self.state.add_transition ('q', 'NUMBER_1', None, 'INIT')
    249 
    250         # \E[?47h switch to alternate screen
    251         # \E[?47l restores to normal screen from alternate screen.
    252         self.state.add_transition_list (string.digits, 'MODECRAP', DoStartNumber, 'MODECRAP_NUM')
    253         self.state.add_transition_list (string.digits, 'MODECRAP_NUM', DoBuildNumber, 'MODECRAP_NUM')
    254         self.state.add_transition ('l', 'MODECRAP_NUM', None, 'INIT')
    255         self.state.add_transition ('h', 'MODECRAP_NUM', None, 'INIT')
    256 
    257 #RM   Reset Mode                Esc [ Ps l                   none
    258         self.state.add_transition (';', 'NUMBER_1', None, 'SEMICOLON')
    259         self.state.add_transition_any ('SEMICOLON', DoLog, 'INIT')
    260         self.state.add_transition_list (string.digits, 'SEMICOLON', DoStartNumber, 'NUMBER_2')
    261         self.state.add_transition_list (string.digits, 'NUMBER_2', DoBuildNumber, 'NUMBER_2')
    262         self.state.add_transition_any ('NUMBER_2', DoLog, 'INIT')
    263         self.state.add_transition ('H', 'NUMBER_2', DoHome, 'INIT')
    264         self.state.add_transition ('f', 'NUMBER_2', DoHome, 'INIT')
    265         self.state.add_transition ('r', 'NUMBER_2', DoScrollRegion, 'INIT')
    266         ### It gets worse... the 'm' code can have infinite number of
    267         ### number;number;number before it. I've never seen more than two,
    268         ### but the specs say it's allowed. crap!
    269         self.state.add_transition ('m', 'NUMBER_2', None, 'INIT')
    270         ### LED control. Same problem as 'm' code.
    271         self.state.add_transition ('q', 'NUMBER_2', None, 'INIT')
    272         self.state.add_transition (';', 'NUMBER_2', None, 'SEMICOLON_X')
    273 
    274         # Create a state for 'q' and 'm' which allows an infinite number of ignored numbers
    275         self.state.add_transition_any ('SEMICOLON_X', DoLog, 'INIT')
    276         self.state.add_transition_list (string.digits, 'SEMICOLON_X', None, 'NUMBER_X')
    277         self.state.add_transition_any ('NUMBER_X', DoLog, 'INIT')
    278         self.state.add_transition ('m', 'NUMBER_X', None, 'INIT')
    279         self.state.add_transition ('q', 'NUMBER_X', None, 'INIT')
    280         self.state.add_transition (';', 'NUMBER_2', None, 'SEMICOLON_X')
    281 
    282     def process (self, c):
    283 
    284         self.state.process(c)
    285 
    286     def process_list (self, l):
    287 
    288         self.write(l)
    289 
    290     def write (self, s):
    291 
    292         for c in s:
    293             self.process(c)
    294 
    295     def flush (self):
    296 
    297         pass
    298 
    299     def write_ch (self, ch):
    300 
    301         """This puts a character at the current cursor position. The cursor
    302         position is moved forward with wrap-around, but no scrolling is done if
    303         the cursor hits the lower-right corner of the screen. """
    304 
    305         #\r and \n both produce a call to cr() and lf(), respectively.
    306         ch = ch[0]
    307 
    308         if ch == '\r':
    309             self.cr()
    310             return
    311         if ch == '\n':
    312             self.crlf()
    313             return
    314         if ch == chr(screen.BS):
    315             self.cursor_back()
    316             return
    317         if ch not in string.printable:
    318             fout = open ('log', 'a')
    319             fout.write ('Nonprint: ' + str(ord(ch)) + '\n')
    320             fout.close()
    321             return
    322         self.put_abs(self.cur_r, self.cur_c, ch)
    323         old_r = self.cur_r
    324         old_c = self.cur_c
    325         self.cursor_forward()
    326         if old_c == self.cur_c:
    327             self.cursor_down()
    328             if old_r != self.cur_r:
    329                 self.cursor_home (self.cur_r, 1)
    330             else:
    331                 self.scroll_up ()
    332                 self.cursor_home (self.cur_r, 1)
    333                 self.erase_line()
    334 
    335 #    def test (self):
    336 #
    337 #        import sys
    338 #        write_text = 'I\'ve got a ferret sticking up my nose.\n' + \
    339 #        '(He\'s got a ferret sticking up his nose.)\n' + \
    340 #        'How it got there I can\'t tell\n' + \
    341 #        'But now it\'s there it hurts like hell\n' + \
    342 #        'And what is more it radically affects my sense of smell.\n' + \
    343 #        '(His sense of smell.)\n' + \
    344 #        'I can see a bare-bottomed mandril.\n' + \
    345 #        '(Slyly eyeing his other nostril.)\n' + \
    346 #        'If it jumps inside there too I really don\'t know what to do\n' + \
    347 #        'I\'ll be the proud posessor of a kind of nasal zoo.\n' + \
    348 #        '(A nasal zoo.)\n' + \
    349 #        'I\'ve got a ferret sticking up my nose.\n' + \
    350 #        '(And what is worst of all it constantly explodes.)\n' + \
    351 #        '"Ferrets don\'t explode," you say\n' + \
    352 #        'But it happened nine times yesterday\n' + \
    353 #        'And I should know for each time I was standing in the way.\n' + \
    354 #        'I\'ve got a ferret sticking up my nose.\n' + \
    355 #        '(He\'s got a ferret sticking up his nose.)\n' + \
    356 #        'How it got there I can\'t tell\n' + \
    357 #        'But now it\'s there it hurts like hell\n' + \
    358 #        'And what is more it radically affects my sense of smell.\n' + \
    359 #        '(His sense of smell.)'
    360 #        self.fill('.')
    361 #        self.cursor_home()
    362 #        for c in write_text:
    363 #            self.write_ch (c)
    364 #        print str(self)
    365 #
    366 #if __name__ == '__main__':
    367 #    t = ANSI(6,65)
    368 #    t.test()
    369