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