Johnny Chen | 8a3c043 | 2011-03-11 20:13:06 +0000 | [diff] [blame] | 1 | """This implements an ANSI terminal emulator as a subclass of screen. |
| 2 | |
| 3 | $Id: ANSI.py 491 2007-12-16 20:04:57Z noah $ |
| 4 | """ |
| 5 | # references: |
| 6 | # http://www.retards.org/terminals/vt102.html |
| 7 | # http://vt100.net/docs/vt102-ug/contents.html |
| 8 | # http://vt100.net/docs/vt220-rm/ |
| 9 | # http://www.termsys.demon.co.uk/vtansi.htm |
| 10 | |
| 11 | import screen |
| 12 | import FSM |
| 13 | import copy |
| 14 | import string |
| 15 | |
| 16 | def Emit (fsm): |
| 17 | |
| 18 | screen = fsm.memory[0] |
| 19 | screen.write_ch(fsm.input_symbol) |
| 20 | |
| 21 | def StartNumber (fsm): |
| 22 | |
| 23 | fsm.memory.append (fsm.input_symbol) |
| 24 | |
| 25 | def BuildNumber (fsm): |
| 26 | |
| 27 | ns = fsm.memory.pop() |
| 28 | ns = ns + fsm.input_symbol |
| 29 | fsm.memory.append (ns) |
| 30 | |
| 31 | def DoBackOne (fsm): |
| 32 | |
| 33 | screen = fsm.memory[0] |
| 34 | screen.cursor_back () |
| 35 | |
| 36 | def DoBack (fsm): |
| 37 | |
| 38 | count = int(fsm.memory.pop()) |
| 39 | screen = fsm.memory[0] |
| 40 | screen.cursor_back (count) |
| 41 | |
| 42 | def DoDownOne (fsm): |
| 43 | |
| 44 | screen = fsm.memory[0] |
| 45 | screen.cursor_down () |
| 46 | |
| 47 | def DoDown (fsm): |
| 48 | |
| 49 | count = int(fsm.memory.pop()) |
| 50 | screen = fsm.memory[0] |
| 51 | screen.cursor_down (count) |
| 52 | |
| 53 | def DoForwardOne (fsm): |
| 54 | |
| 55 | screen = fsm.memory[0] |
| 56 | screen.cursor_forward () |
| 57 | |
| 58 | def DoForward (fsm): |
| 59 | |
| 60 | count = int(fsm.memory.pop()) |
| 61 | screen = fsm.memory[0] |
| 62 | screen.cursor_forward (count) |
| 63 | |
| 64 | def DoUpReverse (fsm): |
| 65 | |
| 66 | screen = fsm.memory[0] |
| 67 | screen.cursor_up_reverse() |
| 68 | |
| 69 | def DoUpOne (fsm): |
| 70 | |
| 71 | screen = fsm.memory[0] |
| 72 | screen.cursor_up () |
| 73 | |
| 74 | def DoUp (fsm): |
| 75 | |
| 76 | count = int(fsm.memory.pop()) |
| 77 | screen = fsm.memory[0] |
| 78 | screen.cursor_up (count) |
| 79 | |
| 80 | def DoHome (fsm): |
| 81 | |
| 82 | c = int(fsm.memory.pop()) |
| 83 | r = int(fsm.memory.pop()) |
| 84 | screen = fsm.memory[0] |
| 85 | screen.cursor_home (r,c) |
| 86 | |
| 87 | def DoHomeOrigin (fsm): |
| 88 | |
| 89 | c = 1 |
| 90 | r = 1 |
| 91 | screen = fsm.memory[0] |
| 92 | screen.cursor_home (r,c) |
| 93 | |
| 94 | def DoEraseDown (fsm): |
| 95 | |
| 96 | screen = fsm.memory[0] |
| 97 | screen.erase_down() |
| 98 | |
| 99 | def DoErase (fsm): |
| 100 | |
| 101 | arg = int(fsm.memory.pop()) |
| 102 | screen = fsm.memory[0] |
| 103 | if arg == 0: |
| 104 | screen.erase_down() |
| 105 | elif arg == 1: |
| 106 | screen.erase_up() |
| 107 | elif arg == 2: |
| 108 | screen.erase_screen() |
| 109 | |
| 110 | def DoEraseEndOfLine (fsm): |
| 111 | |
| 112 | screen = fsm.memory[0] |
| 113 | screen.erase_end_of_line() |
| 114 | |
| 115 | def DoEraseLine (fsm): |
| 116 | |
| 117 | screen = fsm.memory[0] |
| 118 | if arg == 0: |
| 119 | screen.end_of_line() |
| 120 | elif arg == 1: |
| 121 | screen.start_of_line() |
| 122 | elif arg == 2: |
| 123 | screen.erase_line() |
| 124 | |
| 125 | def DoEnableScroll (fsm): |
| 126 | |
| 127 | screen = fsm.memory[0] |
| 128 | screen.scroll_screen() |
| 129 | |
| 130 | def DoCursorSave (fsm): |
| 131 | |
| 132 | screen = fsm.memory[0] |
| 133 | screen.cursor_save_attrs() |
| 134 | |
| 135 | def DoCursorRestore (fsm): |
| 136 | |
| 137 | screen = fsm.memory[0] |
| 138 | screen.cursor_restore_attrs() |
| 139 | |
| 140 | def DoScrollRegion (fsm): |
| 141 | |
| 142 | screen = fsm.memory[0] |
| 143 | r2 = int(fsm.memory.pop()) |
| 144 | r1 = int(fsm.memory.pop()) |
| 145 | screen.scroll_screen_rows (r1,r2) |
| 146 | |
| 147 | def DoMode (fsm): |
| 148 | |
| 149 | screen = fsm.memory[0] |
| 150 | mode = fsm.memory.pop() # Should be 4 |
| 151 | # screen.setReplaceMode () |
| 152 | |
| 153 | def Log (fsm): |
| 154 | |
| 155 | screen = fsm.memory[0] |
| 156 | fsm.memory = [screen] |
| 157 | fout = open ('log', 'a') |
| 158 | fout.write (fsm.input_symbol + ',' + fsm.current_state + '\n') |
| 159 | fout.close() |
| 160 | |
| 161 | class term (screen.screen): |
| 162 | """This is a placeholder. |
| 163 | In theory I might want to add other terminal types. |
| 164 | """ |
| 165 | def __init__ (self, r=24, c=80): |
| 166 | screen.screen.__init__(self, r,c) |
| 167 | |
| 168 | class ANSI (term): |
| 169 | |
| 170 | """This class encapsulates a generic terminal. It filters a stream and |
| 171 | maintains the state of a screen object. """ |
| 172 | |
| 173 | def __init__ (self, r=24,c=80): |
| 174 | |
| 175 | term.__init__(self,r,c) |
| 176 | |
| 177 | #self.screen = screen (24,80) |
| 178 | self.state = FSM.FSM ('INIT',[self]) |
| 179 | self.state.set_default_transition (Log, 'INIT') |
| 180 | self.state.add_transition_any ('INIT', Emit, 'INIT') |
| 181 | self.state.add_transition ('\x1b', 'INIT', None, 'ESC') |
| 182 | self.state.add_transition_any ('ESC', Log, 'INIT') |
| 183 | self.state.add_transition ('(', 'ESC', None, 'G0SCS') |
| 184 | self.state.add_transition (')', 'ESC', None, 'G1SCS') |
| 185 | self.state.add_transition_list ('AB012', 'G0SCS', None, 'INIT') |
| 186 | self.state.add_transition_list ('AB012', 'G1SCS', None, 'INIT') |
| 187 | self.state.add_transition ('7', 'ESC', DoCursorSave, 'INIT') |
| 188 | self.state.add_transition ('8', 'ESC', DoCursorRestore, 'INIT') |
| 189 | self.state.add_transition ('M', 'ESC', DoUpReverse, 'INIT') |
| 190 | self.state.add_transition ('>', 'ESC', DoUpReverse, 'INIT') |
| 191 | self.state.add_transition ('<', 'ESC', DoUpReverse, 'INIT') |
| 192 | self.state.add_transition ('=', 'ESC', None, 'INIT') # Selects application keypad. |
| 193 | self.state.add_transition ('#', 'ESC', None, 'GRAPHICS_POUND') |
| 194 | self.state.add_transition_any ('GRAPHICS_POUND', None, 'INIT') |
| 195 | self.state.add_transition ('[', 'ESC', None, 'ELB') |
| 196 | # ELB means Escape Left Bracket. That is ^[[ |
| 197 | self.state.add_transition ('H', 'ELB', DoHomeOrigin, 'INIT') |
| 198 | self.state.add_transition ('D', 'ELB', DoBackOne, 'INIT') |
| 199 | self.state.add_transition ('B', 'ELB', DoDownOne, 'INIT') |
| 200 | self.state.add_transition ('C', 'ELB', DoForwardOne, 'INIT') |
| 201 | self.state.add_transition ('A', 'ELB', DoUpOne, 'INIT') |
| 202 | self.state.add_transition ('J', 'ELB', DoEraseDown, 'INIT') |
| 203 | self.state.add_transition ('K', 'ELB', DoEraseEndOfLine, 'INIT') |
| 204 | self.state.add_transition ('r', 'ELB', DoEnableScroll, 'INIT') |
| 205 | self.state.add_transition ('m', 'ELB', None, 'INIT') |
| 206 | self.state.add_transition ('?', 'ELB', None, 'MODECRAP') |
| 207 | self.state.add_transition_list (string.digits, 'ELB', StartNumber, 'NUMBER_1') |
| 208 | self.state.add_transition_list (string.digits, 'NUMBER_1', BuildNumber, 'NUMBER_1') |
| 209 | self.state.add_transition ('D', 'NUMBER_1', DoBack, 'INIT') |
| 210 | self.state.add_transition ('B', 'NUMBER_1', DoDown, 'INIT') |
| 211 | self.state.add_transition ('C', 'NUMBER_1', DoForward, 'INIT') |
| 212 | self.state.add_transition ('A', 'NUMBER_1', DoUp, 'INIT') |
| 213 | self.state.add_transition ('J', 'NUMBER_1', DoErase, 'INIT') |
| 214 | self.state.add_transition ('K', 'NUMBER_1', DoEraseLine, 'INIT') |
| 215 | self.state.add_transition ('l', 'NUMBER_1', DoMode, 'INIT') |
| 216 | ### It gets worse... the 'm' code can have infinite number of |
| 217 | ### number;number;number before it. I've never seen more than two, |
| 218 | ### but the specs say it's allowed. crap! |
| 219 | self.state.add_transition ('m', 'NUMBER_1', None, 'INIT') |
| 220 | ### LED control. Same problem as 'm' code. |
| 221 | self.state.add_transition ('q', 'NUMBER_1', None, 'INIT') |
| 222 | |
| 223 | # \E[?47h appears to be "switch to alternate screen" |
| 224 | # \E[?47l restores alternate screen... I think. |
| 225 | self.state.add_transition_list (string.digits, 'MODECRAP', StartNumber, 'MODECRAP_NUM') |
| 226 | self.state.add_transition_list (string.digits, 'MODECRAP_NUM', BuildNumber, 'MODECRAP_NUM') |
| 227 | self.state.add_transition ('l', 'MODECRAP_NUM', None, 'INIT') |
| 228 | self.state.add_transition ('h', 'MODECRAP_NUM', None, 'INIT') |
| 229 | |
| 230 | #RM Reset Mode Esc [ Ps l none |
| 231 | self.state.add_transition (';', 'NUMBER_1', None, 'SEMICOLON') |
| 232 | self.state.add_transition_any ('SEMICOLON', Log, 'INIT') |
| 233 | self.state.add_transition_list (string.digits, 'SEMICOLON', StartNumber, 'NUMBER_2') |
| 234 | self.state.add_transition_list (string.digits, 'NUMBER_2', BuildNumber, 'NUMBER_2') |
| 235 | self.state.add_transition_any ('NUMBER_2', Log, 'INIT') |
| 236 | self.state.add_transition ('H', 'NUMBER_2', DoHome, 'INIT') |
| 237 | self.state.add_transition ('f', 'NUMBER_2', DoHome, 'INIT') |
| 238 | self.state.add_transition ('r', 'NUMBER_2', DoScrollRegion, 'INIT') |
| 239 | ### It gets worse... the 'm' code can have infinite number of |
| 240 | ### number;number;number before it. I've never seen more than two, |
| 241 | ### but the specs say it's allowed. crap! |
| 242 | self.state.add_transition ('m', 'NUMBER_2', None, 'INIT') |
| 243 | ### LED control. Same problem as 'm' code. |
| 244 | self.state.add_transition ('q', 'NUMBER_2', None, 'INIT') |
| 245 | |
| 246 | def process (self, c): |
| 247 | |
| 248 | self.state.process(c) |
| 249 | |
| 250 | def process_list (self, l): |
| 251 | |
| 252 | self.write(l) |
| 253 | |
| 254 | def write (self, s): |
| 255 | |
| 256 | for c in s: |
| 257 | self.process(c) |
| 258 | |
| 259 | def flush (self): |
| 260 | |
| 261 | pass |
| 262 | |
| 263 | def write_ch (self, ch): |
| 264 | |
| 265 | """This puts a character at the current cursor position. cursor |
| 266 | position if moved forward with wrap-around, but no scrolling is done if |
| 267 | the cursor hits the lower-right corner of the screen. """ |
| 268 | |
| 269 | #\r and \n both produce a call to crlf(). |
| 270 | ch = ch[0] |
| 271 | |
| 272 | if ch == '\r': |
| 273 | # self.crlf() |
| 274 | return |
| 275 | if ch == '\n': |
| 276 | self.crlf() |
| 277 | return |
| 278 | if ch == chr(screen.BS): |
| 279 | self.cursor_back() |
| 280 | self.put_abs(self.cur_r, self.cur_c, ' ') |
| 281 | return |
| 282 | |
| 283 | if ch not in string.printable: |
| 284 | fout = open ('log', 'a') |
| 285 | fout.write ('Nonprint: ' + str(ord(ch)) + '\n') |
| 286 | fout.close() |
| 287 | return |
| 288 | self.put_abs(self.cur_r, self.cur_c, ch) |
| 289 | old_r = self.cur_r |
| 290 | old_c = self.cur_c |
| 291 | self.cursor_forward() |
| 292 | if old_c == self.cur_c: |
| 293 | self.cursor_down() |
| 294 | if old_r != self.cur_r: |
| 295 | self.cursor_home (self.cur_r, 1) |
| 296 | else: |
| 297 | self.scroll_up () |
| 298 | self.cursor_home (self.cur_r, 1) |
| 299 | self.erase_line() |
| 300 | |
| 301 | # def test (self): |
| 302 | # |
| 303 | # import sys |
| 304 | # write_text = 'I\'ve got a ferret sticking up my nose.\n' + \ |
| 305 | # '(He\'s got a ferret sticking up his nose.)\n' + \ |
| 306 | # 'How it got there I can\'t tell\n' + \ |
| 307 | # 'But now it\'s there it hurts like hell\n' + \ |
| 308 | # 'And what is more it radically affects my sense of smell.\n' + \ |
| 309 | # '(His sense of smell.)\n' + \ |
| 310 | # 'I can see a bare-bottomed mandril.\n' + \ |
| 311 | # '(Slyly eyeing his other nostril.)\n' + \ |
| 312 | # 'If it jumps inside there too I really don\'t know what to do\n' + \ |
| 313 | # 'I\'ll be the proud posessor of a kind of nasal zoo.\n' + \ |
| 314 | # '(A nasal zoo.)\n' + \ |
| 315 | # 'I\'ve got a ferret sticking up my nose.\n' + \ |
| 316 | # '(And what is worst of all it constantly explodes.)\n' + \ |
| 317 | # '"Ferrets don\'t explode," you say\n' + \ |
| 318 | # 'But it happened nine times yesterday\n' + \ |
| 319 | # 'And I should know for each time I was standing in the way.\n' + \ |
| 320 | # 'I\'ve got a ferret sticking up my nose.\n' + \ |
| 321 | # '(He\'s got a ferret sticking up his nose.)\n' + \ |
| 322 | # 'How it got there I can\'t tell\n' + \ |
| 323 | # 'But now it\'s there it hurts like hell\n' + \ |
| 324 | # 'And what is more it radically affects my sense of smell.\n' + \ |
| 325 | # '(His sense of smell.)' |
| 326 | # self.fill('.') |
| 327 | # self.cursor_home() |
| 328 | # for c in write_text: |
| 329 | # self.write_ch (c) |
| 330 | # print str(self) |
| 331 | # |
| 332 | #if __name__ == '__main__': |
| 333 | # t = ANSI(6,65) |
| 334 | # t.test() |