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.
28
NUL = 0 # Fill character; ignored on input.
29
ENQ = 5 # Transmit answerback message.
30
BEL = 7 # Ring the bell.
31
BS = 8 # Move cursor left.
32
HT = 9 # Move cursor to next tab stop.
36
CR = 13 # Move cursor to left margin or newline.
37
SO = 14 # Invoke G1 character set.
38
SI = 15 # Invoke G0 character set.
39
XON = 17 # Resume transmission.
40
XOFF = 19 # Halt transmission.
41
CAN = 24 # Cancel escape sequence.
42
SUB = 26 # Same as CAN.
43
ESC = 27 # Introduce a control sequence.
44
DEL = 127 # Fill character; ignored on input.
45
SPACE = chr(32) # Space or blank character.
47
def constrain (n, min, max):
49
'''This returns a number, n constrained to the min and max bounds. '''
58
'''This object maintains the state of a virtual text screen as a
59
rectangluar array. This maintains a virtual cursor position and handles
60
scrolling as characters are added. This supports most of the methods needed
61
by an ANSI text screen. Row and column indexes are 1-based (not zero-based,
64
def __init__ (self, r=24,c=80):
65
'''This initializes a blank screen of the given dimensions.'''
73
self.scroll_row_start = 1
74
self.scroll_row_end = self.rows
75
self.w = [ [SPACE] * self.cols for c in range(self.rows)]
78
'''This returns a printable representation of the screen. The end of
79
each screen line is terminated by a newline. '''
81
return '\n'.join ([ ''.join(c) for c in self.w ])
84
'''This returns a copy of the screen as a string. This is similar to
85
__str__ except that lines are not terminated with line feeds. '''
87
return ''.join ([ ''.join(c) for c in self.w ])
90
'''This returns a copy of the screen as a string with an ASCII text box
91
around the screen border. This is similar to __str__ except that it
94
top_bot = '+' + '-'*self.cols + '+\n'
95
return top_bot + '\n'.join(['|'+line+'|' for line in str(self).split('\n')]) + '\n' + top_bot
97
def fill (self, ch=SPACE):
99
self.fill_region (1,1,self.rows,self.cols, ch)
101
def fill_region (self, rs,cs, re,ce, ch=SPACE):
103
rs = constrain (rs, 1, self.rows)
104
re = constrain (re, 1, self.rows)
105
cs = constrain (cs, 1, self.cols)
106
ce = constrain (ce, 1, self.cols)
111
for r in range (rs, re+1):
112
for c in range (cs, ce + 1):
113
self.put_abs (r,c,ch)
116
'''This moves the cursor to the beginning (col 1) of the current row.
119
self.cursor_home (self.cur_r, 1)
122
'''This moves the cursor down with scrolling.
127
if old_r == self.cur_r:
132
'''This advances the cursor with CRLF properties.
133
The cursor will line wrap and the screen may scroll.
140
'''This is an alias for crlf().
145
def put_abs (self, r, c, ch):
146
'''Screen array starts at 1 index.'''
148
r = constrain (r, 1, self.rows)
149
c = constrain (c, 1, self.cols)
151
self.w[r-1][c-1] = ch
154
'''This puts a characters at the current cursor position.
157
self.put_abs (self.cur_r, self.cur_c, ch)
159
def insert_abs (self, r, c, ch):
160
'''This inserts a character at (r,c). Everything under
161
and to the right is shifted right one character.
162
The last character of the line is lost.
165
r = constrain (r, 1, self.rows)
166
c = constrain (c, 1, self.cols)
167
for ci in range (self.cols, c, -1):
168
self.put_abs (r,ci, self.get_abs(r,ci-1))
169
self.put_abs (r,c,ch)
171
def insert (self, ch):
173
self.insert_abs (self.cur_r, self.cur_c, ch)
175
def get_abs (self, r, c):
177
r = constrain (r, 1, self.rows)
178
c = constrain (c, 1, self.cols)
179
return self.w[r-1][c-1]
183
self.get_abs (self.cur_r, self.cur_c)
185
def get_region (self, rs,cs, re,ce):
186
'''This returns a list of lines representing the region.
189
rs = constrain (rs, 1, self.rows)
190
re = constrain (re, 1, self.rows)
191
cs = constrain (cs, 1, self.cols)
192
ce = constrain (ce, 1, self.cols)
198
for r in range (rs, re+1):
200
for c in range (cs, ce + 1):
201
ch = self.get_abs (r,c)
206
def cursor_constrain (self):
207
'''This keeps the cursor within the screen area.
210
self.cur_r = constrain (self.cur_r, 1, self.rows)
211
self.cur_c = constrain (self.cur_c, 1, self.cols)
213
def cursor_home (self, r=1, c=1): # <ESC>[{ROW};{COLUMN}H
217
self.cursor_constrain ()
219
def cursor_back (self,count=1): # <ESC>[{COUNT}D (not confused with down)
221
self.cur_c = self.cur_c - count
222
self.cursor_constrain ()
224
def cursor_down (self,count=1): # <ESC>[{COUNT}B (not confused with back)
226
self.cur_r = self.cur_r + count
227
self.cursor_constrain ()
229
def cursor_forward (self,count=1): # <ESC>[{COUNT}C
231
self.cur_c = self.cur_c + count
232
self.cursor_constrain ()
234
def cursor_up (self,count=1): # <ESC>[{COUNT}A
236
self.cur_r = self.cur_r - count
237
self.cursor_constrain ()
239
def cursor_up_reverse (self): # <ESC> M (called RI -- Reverse Index)
243
if old_r == self.cur_r:
246
def cursor_force_position (self, r, c): # <ESC>[{ROW};{COLUMN}f
247
'''Identical to Cursor Home.'''
249
self.cursor_home (r, c)
251
def cursor_save (self): # <ESC>[s
252
'''Save current cursor position.'''
254
self.cursor_save_attrs()
256
def cursor_unsave (self): # <ESC>[u
257
'''Restores cursor position after a Save Cursor.'''
259
self.cursor_restore_attrs()
261
def cursor_save_attrs (self): # <ESC>7
262
'''Save current cursor position.'''
264
self.cur_saved_r = self.cur_r
265
self.cur_saved_c = self.cur_c
267
def cursor_restore_attrs (self): # <ESC>8
268
'''Restores cursor position after a Save Cursor.'''
270
self.cursor_home (self.cur_saved_r, self.cur_saved_c)
272
def scroll_constrain (self):
273
'''This keeps the scroll region within the screen region.'''
275
if self.scroll_row_start <= 0:
276
self.scroll_row_start = 1
277
if self.scroll_row_end > self.rows:
278
self.scroll_row_end = self.rows
280
def scroll_screen (self): # <ESC>[r
281
'''Enable scrolling for entire display.'''
283
self.scroll_row_start = 1
284
self.scroll_row_end = self.rows
286
def scroll_screen_rows (self, rs, re): # <ESC>[{start};{end}r
287
'''Enable scrolling from row {start} to row {end}.'''
289
self.scroll_row_start = rs
290
self.scroll_row_end = re
291
self.scroll_constrain()
293
def scroll_down (self): # <ESC>D
294
'''Scroll display down one line.'''
296
# Screen is indexed from 1, but arrays are indexed from 0.
297
s = self.scroll_row_start - 1
298
e = self.scroll_row_end - 1
299
self.w[s+1:e+1] = copy.deepcopy(self.w[s:e])
301
def scroll_up (self): # <ESC>M
302
'''Scroll display up one line.'''
304
# Screen is indexed from 1, but arrays are indexed from 0.
305
s = self.scroll_row_start - 1
306
e = self.scroll_row_end - 1
307
self.w[s:e] = copy.deepcopy(self.w[s+1:e+1])
309
def erase_end_of_line (self): # <ESC>[0K -or- <ESC>[K
310
'''Erases from the current cursor position to the end of the current
313
self.fill_region (self.cur_r, self.cur_c, self.cur_r, self.cols)
315
def erase_start_of_line (self): # <ESC>[1K
316
'''Erases from the current cursor position to the start of the current
319
self.fill_region (self.cur_r, 1, self.cur_r, self.cur_c)
321
def erase_line (self): # <ESC>[2K
322
'''Erases the entire current line.'''
324
self.fill_region (self.cur_r, 1, self.cur_r, self.cols)
326
def erase_down (self): # <ESC>[0J -or- <ESC>[J
327
'''Erases the screen from the current line down to the bottom of the
330
self.erase_end_of_line ()
331
self.fill_region (self.cur_r + 1, 1, self.rows, self.cols)
333
def erase_up (self): # <ESC>[1J
334
'''Erases the screen from the current line up to the top of the
337
self.erase_start_of_line ()
338
self.fill_region (self.cur_r-1, 1, 1, self.cols)
340
def erase_screen (self): # <ESC>[2J
341
'''Erases the screen with the background color.'''
345
def set_tab (self): # <ESC>H
346
'''Sets a tab at the current position.'''
350
def clear_tab (self): # <ESC>[g
351
'''Clears tab at the current position.'''
355
def clear_all_tabs (self): # <ESC>[3g
356
'''Clears all tabs.'''
360
# Insert line Esc [ Pn L
361
# Delete line Esc [ Pn M
362
# Delete character Esc [ Pn P
363
# Scrolling region Esc [ Pn(top);Pn(bot) r