• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 ANSI class
4extends this class to add parsing of ANSI escape codes.
5
6$Id: screen.py 486 2007-07-13 01:04:16Z noah $
7"""
8
9import copy
10
11NUL = 0    # Fill character; ignored on input.
12ENQ = 5    # Transmit answerback message.
13BEL = 7    # Ring the bell.
14BS  = 8    # Move cursor left.
15HT  = 9    # Move cursor to next tab stop.
16LF = 10    # Line feed.
17VT = 11    # Same as LF.
18FF = 12    # Same as LF.
19CR = 13    # Move cursor to left margin or newline.
20SO = 14    # Invoke G1 character set.
21SI = 15    # Invoke G0 character set.
22XON = 17   # Resume transmission.
23XOFF = 19  # Halt transmission.
24CAN = 24   # Cancel escape sequence.
25SUB = 26   # Same as CAN.
26ESC = 27   # Introduce a control sequence.
27DEL = 127  # Fill character; ignored on input.
28SPACE = chr(32) # Space or blank character.
29
30def constrain (n, min, max):
31
32    """This returns a number, n constrained to the min and max bounds. """
33
34    if n < min:
35        return min
36    if n > max:
37        return max
38    return n
39
40class screen:
41
42    """This object maintains the state of a virtual text screen as a
43    rectangluar array. This maintains a virtual cursor position and handles
44    scrolling as characters are added. This supports most of the methods needed
45    by an ANSI text screen. Row and column indexes are 1-based (not zero-based,
46    like arrays). """
47
48    def __init__ (self, r=24,c=80):
49
50        """This initializes a blank scree of the given dimentions."""
51
52        self.rows = r
53        self.cols = c
54        self.cur_r = 1
55        self.cur_c = 1
56        self.cur_saved_r = 1
57        self.cur_saved_c = 1
58        self.scroll_row_start = 1
59        self.scroll_row_end = self.rows
60        self.w = [ [SPACE] * self.cols for c in range(self.rows)]
61
62    def __str__ (self):
63
64        """This returns a printable representation of the screen. The end of
65        each screen line is terminated by a newline. """
66
67        return '\n'.join ([ ''.join(c) for c in self.w ])
68
69    def dump (self):
70
71        """This returns a copy of the screen as a string. This is similar to
72        __str__ except that lines are not terminated with line feeds. """
73
74        return ''.join ([ ''.join(c) for c in self.w ])
75
76    def pretty (self):
77
78        """This returns a copy of the screen as a string with an ASCII text box
79        around the screen border. This is similar to __str__ except that it
80        adds a box. """
81
82        top_bot = '+' + '-'*self.cols + '+\n'
83        return top_bot + '\n'.join(['|'+line+'|' for line in str(self).split('\n')]) + '\n' + top_bot
84
85    def fill (self, ch=SPACE):
86
87        self.fill_region (1,1,self.rows,self.cols, ch)
88
89    def fill_region (self, rs,cs, re,ce, ch=SPACE):
90
91        rs = constrain (rs, 1, self.rows)
92        re = constrain (re, 1, self.rows)
93        cs = constrain (cs, 1, self.cols)
94        ce = constrain (ce, 1, self.cols)
95        if rs > re:
96            rs, re = re, rs
97        if cs > ce:
98            cs, ce = ce, cs
99        for r in range (rs, re+1):
100            for c in range (cs, ce + 1):
101                self.put_abs (r,c,ch)
102
103    def cr (self):
104
105        """This moves the cursor to the beginning (col 1) of the current row.
106        """
107
108        self.cursor_home (self.cur_r, 1)
109
110    def lf (self):
111
112        """This moves the cursor down with scrolling.
113        """
114
115        old_r = self.cur_r
116        self.cursor_down()
117        if old_r == self.cur_r:
118            self.scroll_up ()
119            self.erase_line()
120
121    def crlf (self):
122
123        """This advances the cursor with CRLF properties.
124        The cursor will line wrap and the screen may scroll.
125        """
126
127        self.cr ()
128        self.lf ()
129
130    def newline (self):
131
132        """This is an alias for crlf().
133        """
134
135        self.crlf()
136
137    def put_abs (self, r, c, ch):
138
139        """Screen array starts at 1 index."""
140
141        r = constrain (r, 1, self.rows)
142        c = constrain (c, 1, self.cols)
143        ch = str(ch)[0]
144        self.w[r-1][c-1] = ch
145
146    def put (self, ch):
147
148        """This puts a characters at the current cursor position.
149        """
150
151        self.put_abs (self.cur_r, self.cur_c, ch)
152
153    def insert_abs (self, r, c, ch):
154
155        """This inserts a character at (r,c). Everything under
156        and to the right is shifted right one character.
157        The last character of the line is lost.
158        """
159
160        r = constrain (r, 1, self.rows)
161        c = constrain (c, 1, self.cols)
162        for ci in range (self.cols, c, -1):
163            self.put_abs (r,ci, self.get_abs(r,ci-1))
164        self.put_abs (r,c,ch)
165
166    def insert (self, ch):
167
168        self.insert_abs (self.cur_r, self.cur_c, ch)
169
170    def get_abs (self, r, c):
171
172        r = constrain (r, 1, self.rows)
173        c = constrain (c, 1, self.cols)
174        return self.w[r-1][c-1]
175
176    def get (self):
177
178        self.get_abs (self.cur_r, self.cur_c)
179
180    def get_region (self, rs,cs, re,ce):
181
182        """This returns a list of lines representing the region.
183        """
184
185        rs = constrain (rs, 1, self.rows)
186        re = constrain (re, 1, self.rows)
187        cs = constrain (cs, 1, self.cols)
188        ce = constrain (ce, 1, self.cols)
189        if rs > re:
190            rs, re = re, rs
191        if cs > ce:
192            cs, ce = ce, cs
193        sc = []
194        for r in range (rs, re+1):
195            line = ''
196            for c in range (cs, ce + 1):
197                ch = self.get_abs (r,c)
198                line = line + ch
199            sc.append (line)
200        return sc
201
202    def cursor_constrain (self):
203
204        """This keeps the cursor within the screen area.
205        """
206
207        self.cur_r = constrain (self.cur_r, 1, self.rows)
208        self.cur_c = constrain (self.cur_c, 1, self.cols)
209
210    def cursor_home (self, r=1, c=1): # <ESC>[{ROW};{COLUMN}H
211
212        self.cur_r = r
213        self.cur_c = c
214        self.cursor_constrain ()
215
216    def cursor_back (self,count=1): # <ESC>[{COUNT}D (not confused with down)
217
218        self.cur_c = self.cur_c - count
219        self.cursor_constrain ()
220
221    def cursor_down (self,count=1): # <ESC>[{COUNT}B (not confused with back)
222
223        self.cur_r = self.cur_r + count
224        self.cursor_constrain ()
225
226    def cursor_forward (self,count=1): # <ESC>[{COUNT}C
227
228        self.cur_c = self.cur_c + count
229        self.cursor_constrain ()
230
231    def cursor_up (self,count=1): # <ESC>[{COUNT}A
232
233        self.cur_r = self.cur_r - count
234        self.cursor_constrain ()
235
236    def cursor_up_reverse (self): # <ESC> M   (called RI -- Reverse Index)
237
238        old_r = self.cur_r
239        self.cursor_up()
240        if old_r == self.cur_r:
241            self.scroll_up()
242
243    def cursor_force_position (self, r, c): # <ESC>[{ROW};{COLUMN}f
244
245        """Identical to Cursor Home."""
246
247        self.cursor_home (r, c)
248
249    def cursor_save (self): # <ESC>[s
250
251        """Save current cursor position."""
252
253        self.cursor_save_attrs()
254
255    def cursor_unsave (self): # <ESC>[u
256
257        """Restores cursor position after a Save Cursor."""
258
259        self.cursor_restore_attrs()
260
261    def cursor_save_attrs (self): # <ESC>7
262
263        """Save current cursor position."""
264
265        self.cur_saved_r = self.cur_r
266        self.cur_saved_c = self.cur_c
267
268    def cursor_restore_attrs (self): # <ESC>8
269
270        """Restores cursor position after a Save Cursor."""
271
272        self.cursor_home (self.cur_saved_r, self.cur_saved_c)
273
274    def scroll_constrain (self):
275
276        """This keeps the scroll region within the screen region."""
277
278        if self.scroll_row_start <= 0:
279            self.scroll_row_start = 1
280        if self.scroll_row_end > self.rows:
281            self.scroll_row_end = self.rows
282
283    def scroll_screen (self): # <ESC>[r
284
285        """Enable scrolling for entire display."""
286
287        self.scroll_row_start = 1
288        self.scroll_row_end = self.rows
289
290    def scroll_screen_rows (self, rs, re): # <ESC>[{start};{end}r
291
292        """Enable scrolling from row {start} to row {end}."""
293
294        self.scroll_row_start = rs
295        self.scroll_row_end = re
296        self.scroll_constrain()
297
298    def scroll_down (self): # <ESC>D
299
300        """Scroll display down one line."""
301
302        # Screen is indexed from 1, but arrays are indexed from 0.
303        s = self.scroll_row_start - 1
304        e = self.scroll_row_end - 1
305        self.w[s+1:e+1] = copy.deepcopy(self.w[s:e])
306
307    def scroll_up (self): # <ESC>M
308
309        """Scroll display up one line."""
310
311        # Screen is indexed from 1, but arrays are indexed from 0.
312        s = self.scroll_row_start - 1
313        e = self.scroll_row_end - 1
314        self.w[s:e] = copy.deepcopy(self.w[s+1:e+1])
315
316    def erase_end_of_line (self): # <ESC>[0K -or- <ESC>[K
317
318        """Erases from the current cursor position to the end of the current
319        line."""
320
321        self.fill_region (self.cur_r, self.cur_c, self.cur_r, self.cols)
322
323    def erase_start_of_line (self): # <ESC>[1K
324
325        """Erases from the current cursor position to the start of the current
326        line."""
327
328        self.fill_region (self.cur_r, 1, self.cur_r, self.cur_c)
329
330    def erase_line (self): # <ESC>[2K
331
332        """Erases the entire current line."""
333
334        self.fill_region (self.cur_r, 1, self.cur_r, self.cols)
335
336    def erase_down (self): # <ESC>[0J -or- <ESC>[J
337
338        """Erases the screen from the current line down to the bottom of the
339        screen."""
340
341        self.erase_end_of_line ()
342        self.fill_region (self.cur_r + 1, 1, self.rows, self.cols)
343
344    def erase_up (self): # <ESC>[1J
345
346        """Erases the screen from the current line up to the top of the
347        screen."""
348
349        self.erase_start_of_line ()
350        self.fill_region (self.cur_r-1, 1, 1, self.cols)
351
352    def erase_screen (self): # <ESC>[2J
353
354        """Erases the screen with the background color."""
355
356        self.fill ()
357
358    def set_tab (self): # <ESC>H
359
360        """Sets a tab at the current position."""
361
362        pass
363
364    def clear_tab (self): # <ESC>[g
365
366        """Clears tab at the current position."""
367
368        pass
369
370    def clear_all_tabs (self): # <ESC>[3g
371
372        """Clears all tabs."""
373
374        pass
375
376#        Insert line             Esc [ Pn L
377#        Delete line             Esc [ Pn M
378#        Delete character        Esc [ Pn P
379#        Scrolling region        Esc [ Pn(top);Pn(bot) r
380
381