1"""This implements an ANSI terminal emulator as a subclass of screen.
2
3$Id: ANSI.py 491 2007-12-16 20:04:57Z noah $
4"""
5# references:
6#     http://www.retards.org/terminals/vt102.html
7#     http://vt100.net/docs/vt102-ug/contents.html
8#     http://vt100.net/docs/vt220-rm/
9#     http://www.termsys.demon.co.uk/vtansi.htm
10
11import screen
12import FSM
13import copy
14import string
15
16def Emit (fsm):
17
18    screen = fsm.memory[0]
19    screen.write_ch(fsm.input_symbol)
20
21def StartNumber (fsm):
22
23    fsm.memory.append (fsm.input_symbol)
24
25def BuildNumber (fsm):
26
27    ns = fsm.memory.pop()
28    ns = ns + fsm.input_symbol
29    fsm.memory.append (ns)
30
31def DoBackOne (fsm):
32
33    screen = fsm.memory[0]
34    screen.cursor_back ()
35
36def DoBack (fsm):
37
38    count = int(fsm.memory.pop())
39    screen = fsm.memory[0]
40    screen.cursor_back (count)
41
42def DoDownOne (fsm):
43
44    screen = fsm.memory[0]
45    screen.cursor_down ()
46
47def DoDown (fsm):
48
49    count = int(fsm.memory.pop())
50    screen = fsm.memory[0]
51    screen.cursor_down (count)
52
53def DoForwardOne (fsm):
54
55    screen = fsm.memory[0]
56    screen.cursor_forward ()
57
58def DoForward (fsm):
59
60    count = int(fsm.memory.pop())
61    screen = fsm.memory[0]
62    screen.cursor_forward (count)
63
64def DoUpReverse (fsm):
65
66    screen = fsm.memory[0]
67    screen.cursor_up_reverse()
68
69def DoUpOne (fsm):
70
71    screen = fsm.memory[0]
72    screen.cursor_up ()
73
74def DoUp (fsm):
75
76    count = int(fsm.memory.pop())
77    screen = fsm.memory[0]
78    screen.cursor_up (count)
79
80def DoHome (fsm):
81
82    c = int(fsm.memory.pop())
83    r = int(fsm.memory.pop())
84    screen = fsm.memory[0]
85    screen.cursor_home (r,c)
86
87def DoHomeOrigin (fsm):
88
89    c = 1
90    r = 1
91    screen = fsm.memory[0]
92    screen.cursor_home (r,c)
93
94def DoEraseDown (fsm):
95
96    screen = fsm.memory[0]
97    screen.erase_down()
98
99def DoErase (fsm):
100
101    arg = int(fsm.memory.pop())
102    screen = fsm.memory[0]
103    if arg == 0:
104        screen.erase_down()
105    elif arg == 1:
106        screen.erase_up()
107    elif arg == 2:
108        screen.erase_screen()
109
110def DoEraseEndOfLine (fsm):
111
112    screen = fsm.memory[0]
113    screen.erase_end_of_line()
114
115def DoEraseLine (fsm):
116
117    screen = fsm.memory[0]
118    if arg == 0:
119        screen.end_of_line()
120    elif arg == 1:
121        screen.start_of_line()
122    elif arg == 2:
123        screen.erase_line()
124
125def DoEnableScroll (fsm):
126
127    screen = fsm.memory[0]
128    screen.scroll_screen()
129
130def DoCursorSave (fsm):
131
132    screen = fsm.memory[0]
133    screen.cursor_save_attrs()
134
135def DoCursorRestore (fsm):
136
137    screen = fsm.memory[0]
138    screen.cursor_restore_attrs()
139
140def DoScrollRegion (fsm):
141
142    screen = fsm.memory[0]
143    r2 = int(fsm.memory.pop())
144    r1 = int(fsm.memory.pop())
145    screen.scroll_screen_rows (r1,r2)
146
147def DoMode (fsm):
148
149    screen = fsm.memory[0]
150    mode = fsm.memory.pop() # Should be 4
151    # screen.setReplaceMode ()
152
153def Log (fsm):
154
155    screen = fsm.memory[0]
156    fsm.memory = [screen]
157    fout = open ('log', 'a')
158    fout.write (fsm.input_symbol + ',' + fsm.current_state + '\n')
159    fout.close()
160
161class term (screen.screen):
162    """This is a placeholder.
163    In theory I might want to add other terminal types.
164    """
165    def __init__ (self, r=24, c=80):
166        screen.screen.__init__(self, r,c)
167
168class ANSI (term):
169
170    """This class encapsulates a generic terminal. It filters a stream and
171    maintains the state of a screen object. """
172
173    def __init__ (self, r=24,c=80):
174
175        term.__init__(self,r,c)
176
177        #self.screen = screen (24,80)
178        self.state = FSM.FSM ('INIT',[self])
179        self.state.set_default_transition (Log, 'INIT')
180        self.state.add_transition_any ('INIT', Emit, 'INIT')
181        self.state.add_transition ('\x1b', 'INIT', None, 'ESC')
182        self.state.add_transition_any ('ESC', Log, 'INIT')
183        self.state.add_transition ('(', 'ESC', None, 'G0SCS')
184        self.state.add_transition (')', 'ESC', None, 'G1SCS')
185        self.state.add_transition_list ('AB012', 'G0SCS', None, 'INIT')
186        self.state.add_transition_list ('AB012', 'G1SCS', None, 'INIT')
187        self.state.add_transition ('7', 'ESC', DoCursorSave, 'INIT')
188        self.state.add_transition ('8', 'ESC', DoCursorRestore, 'INIT')
189        self.state.add_transition ('M', 'ESC', DoUpReverse, 'INIT')
190        self.state.add_transition ('>', 'ESC', DoUpReverse, 'INIT')
191        self.state.add_transition ('<', 'ESC', DoUpReverse, 'INIT')
192        self.state.add_transition ('=', 'ESC', None, 'INIT') # Selects application keypad.
193        self.state.add_transition ('#', 'ESC', None, 'GRAPHICS_POUND')
194        self.state.add_transition_any ('GRAPHICS_POUND', None, 'INIT')
195        self.state.add_transition ('[', 'ESC', None, 'ELB')
196        # ELB means Escape Left Bracket. That is ^[[
197        self.state.add_transition ('H', 'ELB', DoHomeOrigin, 'INIT')
198        self.state.add_transition ('D', 'ELB', DoBackOne, 'INIT')
199        self.state.add_transition ('B', 'ELB', DoDownOne, 'INIT')
200        self.state.add_transition ('C', 'ELB', DoForwardOne, 'INIT')
201        self.state.add_transition ('A', 'ELB', DoUpOne, 'INIT')
202        self.state.add_transition ('J', 'ELB', DoEraseDown, 'INIT')
203        self.state.add_transition ('K', 'ELB', DoEraseEndOfLine, 'INIT')
204        self.state.add_transition ('r', 'ELB', DoEnableScroll, 'INIT')
205        self.state.add_transition ('m', 'ELB', None, 'INIT')
206        self.state.add_transition ('?', 'ELB', None, 'MODECRAP')
207        self.state.add_transition_list (string.digits, 'ELB', StartNumber, 'NUMBER_1')
208        self.state.add_transition_list (string.digits, 'NUMBER_1', BuildNumber, 'NUMBER_1')
209        self.state.add_transition ('D', 'NUMBER_1', DoBack, 'INIT')
210        self.state.add_transition ('B', 'NUMBER_1', DoDown, 'INIT')
211        self.state.add_transition ('C', 'NUMBER_1', DoForward, 'INIT')
212        self.state.add_transition ('A', 'NUMBER_1', DoUp, 'INIT')
213        self.state.add_transition ('J', 'NUMBER_1', DoErase, 'INIT')
214        self.state.add_transition ('K', 'NUMBER_1', DoEraseLine, 'INIT')
215        self.state.add_transition ('l', 'NUMBER_1', DoMode, 'INIT')
216        ### It gets worse... the 'm' code can have infinite number of
217        ### number;number;number before it. I've never seen more than two,
218        ### but the specs say it's allowed. crap!
219        self.state.add_transition ('m', 'NUMBER_1', None, 'INIT')
220        ### LED control. Same problem as 'm' code.
221        self.state.add_transition ('q', 'NUMBER_1', None, 'INIT')
222
223        # \E[?47h appears to be "switch to alternate screen"
224        # \E[?47l restores alternate screen... I think.
225        self.state.add_transition_list (string.digits, 'MODECRAP', StartNumber, 'MODECRAP_NUM')
226        self.state.add_transition_list (string.digits, 'MODECRAP_NUM', BuildNumber, 'MODECRAP_NUM')
227        self.state.add_transition ('l', 'MODECRAP_NUM', None, 'INIT')
228        self.state.add_transition ('h', 'MODECRAP_NUM', None, 'INIT')
229
230#RM   Reset Mode                Esc [ Ps l                   none
231        self.state.add_transition (';', 'NUMBER_1', None, 'SEMICOLON')
232        self.state.add_transition_any ('SEMICOLON', Log, 'INIT')
233        self.state.add_transition_list (string.digits, 'SEMICOLON', StartNumber, 'NUMBER_2')
234        self.state.add_transition_list (string.digits, 'NUMBER_2', BuildNumber, 'NUMBER_2')
235        self.state.add_transition_any ('NUMBER_2', Log, 'INIT')
236        self.state.add_transition ('H', 'NUMBER_2', DoHome, 'INIT')
237        self.state.add_transition ('f', 'NUMBER_2', DoHome, 'INIT')
238        self.state.add_transition ('r', 'NUMBER_2', DoScrollRegion, 'INIT')
239        ### It gets worse... the 'm' code can have infinite number of
240        ### number;number;number before it. I've never seen more than two,
241        ### but the specs say it's allowed. crap!
242        self.state.add_transition ('m', 'NUMBER_2', None, 'INIT')
243        ### LED control. Same problem as 'm' code.
244        self.state.add_transition ('q', 'NUMBER_2', None, 'INIT')
245
246    def process (self, c):
247
248        self.state.process(c)
249
250    def process_list (self, l):
251
252        self.write(l)
253
254    def write (self, s):
255
256        for c in s:
257            self.process(c)
258
259    def flush (self):
260
261        pass
262
263    def write_ch (self, ch):
264
265        """This puts a character at the current cursor position. cursor
266        position if moved forward with wrap-around, but no scrolling is done if
267        the cursor hits the lower-right corner of the screen. """
268
269        #\r and \n both produce a call to crlf().
270        ch = ch[0]
271
272        if ch == '\r':
273        #    self.crlf()
274            return
275        if ch == '\n':
276            self.crlf()
277            return
278        if ch == chr(screen.BS):
279            self.cursor_back()
280            self.put_abs(self.cur_r, self.cur_c, ' ')
281            return
282
283        if ch not in string.printable:
284            fout = open ('log', 'a')
285            fout.write ('Nonprint: ' + str(ord(ch)) + '\n')
286            fout.close()
287            return
288        self.put_abs(self.cur_r, self.cur_c, ch)
289        old_r = self.cur_r
290        old_c = self.cur_c
291        self.cursor_forward()
292        if old_c == self.cur_c:
293            self.cursor_down()
294            if old_r != self.cur_r:
295                self.cursor_home (self.cur_r, 1)
296            else:
297                self.scroll_up ()
298                self.cursor_home (self.cur_r, 1)
299                self.erase_line()
300
301#    def test (self):
302#
303#        import sys
304#        write_text = 'I\'ve got a ferret sticking up my nose.\n' + \
305#        '(He\'s got a ferret sticking up his nose.)\n' + \
306#        'How it got there I can\'t tell\n' + \
307#        'But now it\'s there it hurts like hell\n' + \
308#        'And what is more it radically affects my sense of smell.\n' + \
309#        '(His sense of smell.)\n' + \
310#        'I can see a bare-bottomed mandril.\n' + \
311#        '(Slyly eyeing his other nostril.)\n' + \
312#        'If it jumps inside there too I really don\'t know what to do\n' + \
313#        'I\'ll be the proud posessor of a kind of nasal zoo.\n' + \
314#        '(A nasal zoo.)\n' + \
315#        'I\'ve got a ferret sticking up my nose.\n' + \
316#        '(And what is worst of all it constantly explodes.)\n' + \
317#        '"Ferrets don\'t explode," you say\n' + \
318#        'But it happened nine times yesterday\n' + \
319#        'And I should know for each time I was standing in the way.\n' + \
320#        'I\'ve got a ferret sticking up my nose.\n' + \
321#        '(He\'s got a ferret sticking up his nose.)\n' + \
322#        'How it got there I can\'t tell\n' + \
323#        'But now it\'s there it hurts like hell\n' + \
324#        'And what is more it radically affects my sense of smell.\n' + \
325#        '(His sense of smell.)'
326#        self.fill('.')
327#        self.cursor_home()
328#        for c in write_text:
329#            self.write_ch (c)
330#        print str(self)
331#
332#if __name__ == '__main__':
333#    t = ANSI(6,65)
334#    t.test()
335