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