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
4 :class:`~pexpect.ANSI.ANSI` class extends this class to add parsing of ANSI
9 This license is approved by the OSI and FSF as GPL-compatible.
10 http://opensource.org/licenses/isc-license.txt
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.
32 warnings
.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"),
37 NUL
= 0 # Fill character; ignored on input.
38 ENQ
= 5 # Transmit answerback message.
39 BEL
= 7 # Ring the bell.
40 BS
= 8 # Move cursor left.
41 HT
= 9 # Move cursor to next tab stop.
45 CR
= 13 # Move cursor to left margin or newline.
46 SO
= 14 # Invoke G1 character set.
47 SI
= 15 # Invoke G0 character set.
48 XON
= 17 # Resume transmission.
49 XOFF
= 19 # Halt transmission.
50 CAN
= 24 # Cancel escape sequence.
51 SUB
= 26 # Same as CAN.
52 ESC
= 27 # Introduce a control sequence.
53 DEL
= 127 # Fill character; ignored on input.
54 SPACE
= u
' ' # Space or blank character.
56 PY3
= (sys
.version_info
[0] >= 3)
60 def constrain (n
, min, max):
62 '''This returns a number, n constrained to the min and max bounds. '''
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,
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`.
85 def __init__(self
, r
=24, c
=80, encoding
='latin-1', encoding_errors
='replace'):
86 '''This initializes a blank screen of the given dimensions.'''
90 self
.encoding
= encoding
91 self
.encoding_errors
= encoding_errors
92 if encoding
is not None:
93 self
.decoder
= codecs
.getincrementaldecoder(encoding
)(encoding_errors
)
100 self
.scroll_row_start
= 1
101 self
.scroll_row_end
= self
.rows
102 self
.w
= [ [SPACE
] * self
.cols
for _
in range(self
.rows
)]
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
)
110 raise TypeError("This screen was constructed with encoding=None, "
111 "so it does not handle bytes.")
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.'''
118 return u
'\n'.join ([ u
''.join(c
) for c
in self
.w
])
123 __unicode__
= _unicode
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')
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
136 return u
''.join ([ u
''.join(c
) for c
in self
.w
])
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.'''
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
146 def fill (self
, ch
=SPACE
):
148 if isinstance(ch
, bytes
):
149 ch
= self
._decode
(ch
)
151 self
.fill_region (1,1,self
.rows
,self
.cols
, ch
)
153 def fill_region (self
, rs
,cs
, re
,ce
, ch
=SPACE
):
155 if isinstance(ch
, bytes
):
156 ch
= self
._decode
(ch
)
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
)
166 for r
in range (rs
, re
+1):
167 for c
in range (cs
, ce
+ 1):
168 self
.put_abs (r
,c
,ch
)
171 '''This moves the cursor to the beginning (col 1) of the current row.
174 self
.cursor_home (self
.cur_r
, 1)
177 '''This moves the cursor down with scrolling.
182 if old_r
== self
.cur_r
:
187 '''This advances the cursor with CRLF properties.
188 The cursor will line wrap and the screen may scroll.
195 '''This is an alias for crlf().
200 def put_abs (self
, r
, c
, ch
):
201 '''Screen array starts at 1 index.'''
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]
209 self
.w
[r
-1][c
-1] = ch
212 '''This puts a characters at the current cursor position.
215 if isinstance(ch
, bytes
):
216 ch
= self
._decode
(ch
)
218 self
.put_abs (self
.cur_r
, self
.cur_c
, ch
)
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.
226 if isinstance(ch
, bytes
):
227 ch
= self
._decode
(ch
)
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
)
235 def insert (self
, ch
):
237 if isinstance(ch
, bytes
):
238 ch
= self
._decode
(ch
)
240 self
.insert_abs (self
.cur_r
, self
.cur_c
, ch
)
242 def get_abs (self
, r
, c
):
244 r
= constrain (r
, 1, self
.rows
)
245 c
= constrain (c
, 1, self
.cols
)
246 return self
.w
[r
-1][c
-1]
250 self
.get_abs (self
.cur_r
, self
.cur_c
)
252 def get_region (self
, rs
,cs
, re
,ce
):
253 '''This returns a list of lines representing the region.
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
)
265 for r
in range (rs
, re
+1):
267 for c
in range (cs
, ce
+ 1):
268 ch
= self
.get_abs (r
,c
)
273 def cursor_constrain (self
):
274 '''This keeps the cursor within the screen area.
277 self
.cur_r
= constrain (self
.cur_r
, 1, self
.rows
)
278 self
.cur_c
= constrain (self
.cur_c
, 1, self
.cols
)
280 def cursor_home (self
, r
=1, c
=1): # <ESC>[{ROW};{COLUMN}H
284 self
.cursor_constrain ()
286 def cursor_back (self
,count
=1): # <ESC>[{COUNT}D (not confused with down)
288 self
.cur_c
= self
.cur_c
- count
289 self
.cursor_constrain ()
291 def cursor_down (self
,count
=1): # <ESC>[{COUNT}B (not confused with back)
293 self
.cur_r
= self
.cur_r
+ count
294 self
.cursor_constrain ()
296 def cursor_forward (self
,count
=1): # <ESC>[{COUNT}C
298 self
.cur_c
= self
.cur_c
+ count
299 self
.cursor_constrain ()
301 def cursor_up (self
,count
=1): # <ESC>[{COUNT}A
303 self
.cur_r
= self
.cur_r
- count
304 self
.cursor_constrain ()
306 def cursor_up_reverse (self
): # <ESC> M (called RI -- Reverse Index)
310 if old_r
== self
.cur_r
:
313 def cursor_force_position (self
, r
, c
): # <ESC>[{ROW};{COLUMN}f
314 '''Identical to Cursor Home.'''
316 self
.cursor_home (r
, c
)
318 def cursor_save (self
): # <ESC>[s
319 '''Save current cursor position.'''
321 self
.cursor_save_attrs()
323 def cursor_unsave (self
): # <ESC>[u
324 '''Restores cursor position after a Save Cursor.'''
326 self
.cursor_restore_attrs()
328 def cursor_save_attrs (self
): # <ESC>7
329 '''Save current cursor position.'''
331 self
.cur_saved_r
= self
.cur_r
332 self
.cur_saved_c
= self
.cur_c
334 def cursor_restore_attrs (self
): # <ESC>8
335 '''Restores cursor position after a Save Cursor.'''
337 self
.cursor_home (self
.cur_saved_r
, self
.cur_saved_c
)
339 def scroll_constrain (self
):
340 '''This keeps the scroll region within the screen region.'''
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
347 def scroll_screen (self
): # <ESC>[r
348 '''Enable scrolling for entire display.'''
350 self
.scroll_row_start
= 1
351 self
.scroll_row_end
= self
.rows
353 def scroll_screen_rows (self
, rs
, re
): # <ESC>[{start};{end}r
354 '''Enable scrolling from row {start} to row {end}.'''
356 self
.scroll_row_start
= rs
357 self
.scroll_row_end
= re
358 self
.scroll_constrain()
360 def scroll_down (self
): # <ESC>D
361 '''Scroll display down one line.'''
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
])
368 def scroll_up (self
): # <ESC>M
369 '''Scroll display up one line.'''
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])
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
380 self
.fill_region (self
.cur_r
, self
.cur_c
, self
.cur_r
, self
.cols
)
382 def erase_start_of_line (self
): # <ESC>[1K
383 '''Erases from the current cursor position to the start of the current
386 self
.fill_region (self
.cur_r
, 1, self
.cur_r
, self
.cur_c
)
388 def erase_line (self
): # <ESC>[2K
389 '''Erases the entire current line.'''
391 self
.fill_region (self
.cur_r
, 1, self
.cur_r
, self
.cols
)
393 def erase_down (self
): # <ESC>[0J -or- <ESC>[J
394 '''Erases the screen from the current line down to the bottom of the
397 self
.erase_end_of_line ()
398 self
.fill_region (self
.cur_r
+ 1, 1, self
.rows
, self
.cols
)
400 def erase_up (self
): # <ESC>[1J
401 '''Erases the screen from the current line up to the top of the
404 self
.erase_start_of_line ()
405 self
.fill_region (self
.cur_r
-1, 1, 1, self
.cols
)
407 def erase_screen (self
): # <ESC>[2J
408 '''Erases the screen with the background color.'''
412 def set_tab (self
): # <ESC>H
413 '''Sets a tab at the current position.'''
417 def clear_tab (self
): # <ESC>[g
418 '''Clears tab at the current position.'''
422 def clear_all_tabs (self
): # <ESC>[3g
423 '''Clears all tabs.'''
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