Work towards C SDK (#11). Frenchie is still broken.
[progcomp10.git] / src / link / pexpect / screen.py
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 ANSI class
4 extends this class to add parsing of ANSI escape codes.
5
6 $Id: screen.py 486 2007-07-13 01:04:16Z noah $
7 """
8
9 import copy
10
11 NUL = 0    # Fill character; ignored on input.
12 ENQ = 5    # Transmit answerback message.
13 BEL = 7    # Ring the bell.
14 BS  = 8    # Move cursor left.
15 HT  = 9    # Move cursor to next tab stop.
16 LF = 10    # Line feed.
17 VT = 11    # Same as LF.
18 FF = 12    # Same as LF.
19 CR = 13    # Move cursor to left margin or newline.
20 SO = 14    # Invoke G1 character set.
21 SI = 15    # Invoke G0 character set.
22 XON = 17   # Resume transmission.
23 XOFF = 19  # Halt transmission.
24 CAN = 24   # Cancel escape sequence.
25 SUB = 26   # Same as CAN.
26 ESC = 27   # Introduce a control sequence.
27 DEL = 127  # Fill character; ignored on input.
28 SPACE = chr(32) # Space or blank character.
29
30 def 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
40 class 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

UCC git Repository :: git.ucc.asn.au