1'''This implements an ANSI (VT100) terminal emulator as a subclass of screen.
2
3PEXPECT 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@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
29from . import screen
30from . import FSM
31import string
32
33#
34# The 'Do.*' functions are helper functions for the ANSI class.
35#
36def DoEmit (fsm):
37
38    screen = fsm.memory[0]
39    screen.write_ch(fsm.input_symbol)
40
41def DoStartNumber (fsm):
42
43    fsm.memory.append (fsm.input_symbol)
44
45def DoBuildNumber (fsm):
46
47    ns = fsm.memory.pop()
48    ns = ns + fsm.input_symbol
49    fsm.memory.append (ns)
50
51def DoBackOne (fsm):
52
53    screen = fsm.memory[0]
54    screen.cursor_back ()
55
56def DoBack (fsm):
57
58    count = int(fsm.memory.pop())
59    screen = fsm.memory[0]
60    screen.cursor_back (count)
61
62def DoDownOne (fsm):
63
64    screen = fsm.memory[0]
65    screen.cursor_down ()
66
67def DoDown (fsm):
68
69    count = int(fsm.memory.pop())
70    screen = fsm.memory[0]
71    screen.cursor_down (count)
72
73def DoForwardOne (fsm):
74
75    screen = fsm.memory[0]
76    screen.cursor_forward ()
77
78def DoForward (fsm):
79
80    count = int(fsm.memory.pop())
81    screen = fsm.memory[0]
82    screen.cursor_forward (count)
83
84def DoUpReverse (fsm):
85
86    screen = fsm.memory[0]
87    screen.cursor_up_reverse()
88
89def DoUpOne (fsm):
90
91    screen = fsm.memory[0]
92    screen.cursor_up ()
93
94def DoUp (fsm):
95
96    count = int(fsm.memory.pop())
97    screen = fsm.memory[0]
98    screen.cursor_up (count)
99
100def DoHome (fsm):
101
102    c = int(fsm.memory.pop())
103    r = int(fsm.memory.pop())
104    screen = fsm.memory[0]
105    screen.cursor_home (r,c)
106
107def DoHomeOrigin (fsm):
108
109    c = 1
110    r = 1
111    screen = fsm.memory[0]
112    screen.cursor_home (r,c)
113
114def DoEraseDown (fsm):
115
116    screen = fsm.memory[0]
117    screen.erase_down()
118
119def DoErase (fsm):
120
121    arg = int(fsm.memory.pop())
122    screen = fsm.memory[0]
123    if arg == 0:
124        screen.erase_down()
125    elif arg == 1:
126        screen.erase_up()
127    elif arg == 2:
128        screen.erase_screen()
129
130def DoEraseEndOfLine (fsm):
131
132    screen = fsm.memory[0]
133    screen.erase_end_of_line()
134
135def DoEraseLine (fsm):
136
137    arg = int(fsm.memory.pop())
138    screen = fsm.memory[0]
139    if arg == 0:
140        screen.erase_end_of_line()
141    elif arg == 1:
142        screen.erase_start_of_line()
143    elif arg == 2:
144        screen.erase_line()
145
146def DoEnableScroll (fsm):
147
148    screen = fsm.memory[0]
149    screen.scroll_screen()
150
151def DoCursorSave (fsm):
152
153    screen = fsm.memory[0]
154    screen.cursor_save_attrs()
155
156def DoCursorRestore (fsm):
157
158    screen = fsm.memory[0]
159    screen.cursor_restore_attrs()
160
161def DoScrollRegion (fsm):
162
163    screen = fsm.memory[0]
164    r2 = int(fsm.memory.pop())
165    r1 = int(fsm.memory.pop())
166    screen.scroll_screen_rows (r1,r2)
167
168def DoMode (fsm):
169
170    screen = fsm.memory[0]
171    mode = fsm.memory.pop() # Should be 4
172    # screen.setReplaceMode ()
173
174def DoLog (fsm):
175
176    screen = fsm.memory[0]
177    fsm.memory = [screen]
178    fout = open ('log', 'a')
179    fout.write (fsm.input_symbol + ',' + fsm.current_state + '\n')
180    fout.close()
181
182class term (screen.screen):
183
184    '''This class is an abstract, generic terminal.
185    This does nothing. This is a placeholder that
186    provides a common base class for other terminals
187    such as an ANSI terminal. '''
188
189    def __init__ (self, r=24, c=80, *args, **kwargs):
190
191        screen.screen.__init__(self, r,c,*args,**kwargs)
192
193class ANSI (term):
194    '''This class implements an ANSI (VT100) terminal.
195    It is a stream filter that recognizes ANSI terminal
196    escape sequences and maintains the state of a screen object. '''
197
198    def __init__ (self, r=24,c=80,*args,**kwargs):
199
200        term.__init__(self,r,c,*args,**kwargs)
201
202        #self.screen = screen (24,80)
203        self.state = FSM.FSM ('INIT',[self])
204        self.state.set_default_transition (DoLog, 'INIT')
205        self.state.add_transition_any ('INIT', DoEmit, 'INIT')
206        self.state.add_transition ('\x1b', 'INIT', None, 'ESC')
207        self.state.add_transition_any ('ESC', DoLog, 'INIT')
208        self.state.add_transition ('(', 'ESC', None, 'G0SCS')
209        self.state.add_transition (')', 'ESC', None, 'G1SCS')
210        self.state.add_transition_list ('AB012', 'G0SCS', None, 'INIT')
211        self.state.add_transition_list ('AB012', 'G1SCS', None, 'INIT')
212        self.state.add_transition ('7', 'ESC', DoCursorSave, 'INIT')
213        self.state.add_transition ('8', 'ESC', DoCursorRestore, 'INIT')
214        self.state.add_transition ('M', 'ESC', DoUpReverse, 'INIT')
215        self.state.add_transition ('>', 'ESC', DoUpReverse, 'INIT')
216        self.state.add_transition ('<', 'ESC', DoUpReverse, 'INIT')
217        self.state.add_transition ('=', 'ESC', None, 'INIT') # Selects application keypad.
218        self.state.add_transition ('#', 'ESC', None, 'GRAPHICS_POUND')
219        self.state.add_transition_any ('GRAPHICS_POUND', None, 'INIT')
220        self.state.add_transition ('[', 'ESC', None, 'ELB')
221        # ELB means Escape Left Bracket. That is ^[[
222        self.state.add_transition ('H', 'ELB', DoHomeOrigin, 'INIT')
223        self.state.add_transition ('D', 'ELB', DoBackOne, 'INIT')
224        self.state.add_transition ('B', 'ELB', DoDownOne, 'INIT')
225        self.state.add_transition ('C', 'ELB', DoForwardOne, 'INIT')
226        self.state.add_transition ('A', 'ELB', DoUpOne, 'INIT')
227        self.state.add_transition ('J', 'ELB', DoEraseDown, 'INIT')
228        self.state.add_transition ('K', 'ELB', DoEraseEndOfLine, 'INIT')
229        self.state.add_transition ('r', 'ELB', DoEnableScroll, 'INIT')
230        self.state.add_transition ('m', 'ELB', self.do_sgr, 'INIT')
231        self.state.add_transition ('?', 'ELB', None, 'MODECRAP')
232        self.state.add_transition_list (string.digits, 'ELB', DoStartNumber, 'NUMBER_1')
233        self.state.add_transition_list (string.digits, 'NUMBER_1', DoBuildNumber, 'NUMBER_1')
234        self.state.add_transition ('D', 'NUMBER_1', DoBack, 'INIT')
235        self.state.add_transition ('B', 'NUMBER_1', DoDown, 'INIT')
236        self.state.add_transition ('C', 'NUMBER_1', DoForward, 'INIT')
237        self.state.add_transition ('A', 'NUMBER_1', DoUp, 'INIT')
238        self.state.add_transition ('J', 'NUMBER_1', DoErase, 'INIT')
239        self.state.add_transition ('K', 'NUMBER_1', DoEraseLine, 'INIT')
240        self.state.add_transition ('l', 'NUMBER_1', DoMode, 'INIT')
241        ### It gets worse... the 'm' code can have infinite number of
242        ### number;number;number before it. I've never seen more than two,
243        ### but the specs say it's allowed. crap!
244        self.state.add_transition ('m', 'NUMBER_1', self.do_sgr, 'INIT')
245        ### LED control. Same implementation problem as 'm' code.
246        self.state.add_transition ('q', 'NUMBER_1', self.do_decsca, 'INIT')
247
248        # \E[?47h switch to alternate screen
249        # \E[?47l restores to normal screen from alternate screen.
250        self.state.add_transition_list (string.digits, 'MODECRAP', DoStartNumber, 'MODECRAP_NUM')
251        self.state.add_transition_list (string.digits, 'MODECRAP_NUM', DoBuildNumber, 'MODECRAP_NUM')
252        self.state.add_transition ('l', 'MODECRAP_NUM', self.do_modecrap, 'INIT')
253        self.state.add_transition ('h', 'MODECRAP_NUM', self.do_modecrap, 'INIT')
254
255#RM   Reset Mode                Esc [ Ps l                   none
256        self.state.add_transition (';', 'NUMBER_1', None, 'SEMICOLON')
257        self.state.add_transition_any ('SEMICOLON', DoLog, 'INIT')
258        self.state.add_transition_list (string.digits, 'SEMICOLON', DoStartNumber, 'NUMBER_2')
259        self.state.add_transition_list (string.digits, 'NUMBER_2', DoBuildNumber, 'NUMBER_2')
260        self.state.add_transition_any ('NUMBER_2', DoLog, 'INIT')
261        self.state.add_transition ('H', 'NUMBER_2', DoHome, 'INIT')
262        self.state.add_transition ('f', 'NUMBER_2', DoHome, 'INIT')
263        self.state.add_transition ('r', 'NUMBER_2', DoScrollRegion, 'INIT')
264        ### It gets worse... the 'm' code can have infinite number of
265        ### number;number;number before it. I've never seen more than two,
266        ### but the specs say it's allowed. crap!
267        self.state.add_transition ('m', 'NUMBER_2', self.do_sgr, 'INIT')
268        ### LED control. Same problem as 'm' code.
269        self.state.add_transition ('q', 'NUMBER_2', self.do_decsca, 'INIT')
270        self.state.add_transition (';', 'NUMBER_2', None, 'SEMICOLON_X')
271
272        # Create a state for 'q' and 'm' which allows an infinite number of ignored numbers
273        self.state.add_transition_any ('SEMICOLON_X', DoLog, 'INIT')
274        self.state.add_transition_list (string.digits, 'SEMICOLON_X', DoStartNumber, 'NUMBER_X')
275        self.state.add_transition_list (string.digits, 'NUMBER_X', DoBuildNumber, 'NUMBER_X')
276        self.state.add_transition_any ('NUMBER_X', DoLog, 'INIT')
277        self.state.add_transition ('m', 'NUMBER_X', self.do_sgr, 'INIT')
278        self.state.add_transition ('q', 'NUMBER_X', self.do_decsca, 'INIT')
279        self.state.add_transition (';', 'NUMBER_X', None, 'SEMICOLON_X')
280
281    def process (self, c):
282        """Process a single character. Called by :meth:`write`."""
283        if isinstance(c, bytes):
284            c = self._decode(c)
285        self.state.process(c)
286
287    def process_list (self, l):
288
289        self.write(l)
290
291    def write (self, s):
292        """Process text, writing it to the virtual screen while handling
293        ANSI escape codes.
294        """
295        if isinstance(s, bytes):
296            s = self._decode(s)
297        for c in s:
298            self.process(c)
299
300    def flush (self):
301        pass
302
303    def write_ch (self, ch):
304        '''This puts a character at the current cursor position. The cursor
305        position is moved forward with wrap-around, but no scrolling is done if
306        the cursor hits the lower-right corner of the screen. '''
307
308        if isinstance(ch, bytes):
309            ch = self._decode(ch)
310
311        #\r and \n both produce a call to cr() and lf(), respectively.
312        ch = ch[0]
313
314        if ch == u'\r':
315            self.cr()
316            return
317        if ch == u'\n':
318            self.crlf()
319            return
320        if ch == chr(screen.BS):
321            self.cursor_back()
322            return
323        self.put_abs(self.cur_r, self.cur_c, ch)
324        old_r = self.cur_r
325        old_c = self.cur_c
326        self.cursor_forward()
327        if old_c == self.cur_c:
328            self.cursor_down()
329            if old_r != self.cur_r:
330                self.cursor_home (self.cur_r, 1)
331            else:
332                self.scroll_up ()
333                self.cursor_home (self.cur_r, 1)
334                self.erase_line()
335
336    def do_sgr (self, fsm):
337        '''Select Graphic Rendition, e.g. color. '''
338        screen = fsm.memory[0]
339        fsm.memory = [screen]
340
341    def do_decsca (self, fsm):
342        '''Select character protection attribute. '''
343        screen = fsm.memory[0]
344        fsm.memory = [screen]
345
346    def do_modecrap (self, fsm):
347        '''Handler for \x1b[?<number>h and \x1b[?<number>l. If anyone
348        wanted to actually use these, they'd need to add more states to the
349        FSM rather than just improve or override this method. '''
350        screen = fsm.memory[0]
351        fsm.memory = [screen]
352