ANSI.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. """This implements an ANSI (VT100) terminal emulator as a subclass of screen.
  2. PEXPECT LICENSE
  3. This license is approved by the OSI and FSF as GPL-compatible.
  4. http://opensource.org/licenses/isc-license.txt
  5. Copyright (c) 2012, Noah Spurrier <noah@noah.org>
  6. PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY
  7. PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE
  8. COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES.
  9. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  10. WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  11. MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  12. ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  13. WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  14. ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  15. OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  16. """
  17. # references:
  18. # http://en.wikipedia.org/wiki/ANSI_escape_code
  19. # http://www.retards.org/terminals/vt102.html
  20. # http://vt100.net/docs/vt102-ug/contents.html
  21. # http://vt100.net/docs/vt220-rm/
  22. # http://www.termsys.demon.co.uk/vtansi.htm
  23. import screen
  24. import FSM
  25. import copy
  26. import string
  27. #
  28. # The 'Do.*' functions are helper functions for the ANSI class.
  29. #
  30. def DoEmit (fsm):
  31. screen = fsm.memory[0]
  32. screen.write_ch(fsm.input_symbol)
  33. def DoStartNumber (fsm):
  34. fsm.memory.append (fsm.input_symbol)
  35. def DoBuildNumber (fsm):
  36. ns = fsm.memory.pop()
  37. ns = ns + fsm.input_symbol
  38. fsm.memory.append (ns)
  39. def DoBackOne (fsm):
  40. screen = fsm.memory[0]
  41. screen.cursor_back ()
  42. def DoBack (fsm):
  43. count = int(fsm.memory.pop())
  44. screen = fsm.memory[0]
  45. screen.cursor_back (count)
  46. def DoDownOne (fsm):
  47. screen = fsm.memory[0]
  48. screen.cursor_down ()
  49. def DoDown (fsm):
  50. count = int(fsm.memory.pop())
  51. screen = fsm.memory[0]
  52. screen.cursor_down (count)
  53. def DoForwardOne (fsm):
  54. screen = fsm.memory[0]
  55. screen.cursor_forward ()
  56. def DoForward (fsm):
  57. count = int(fsm.memory.pop())
  58. screen = fsm.memory[0]
  59. screen.cursor_forward (count)
  60. def DoUpReverse (fsm):
  61. screen = fsm.memory[0]
  62. screen.cursor_up_reverse()
  63. def DoUpOne (fsm):
  64. screen = fsm.memory[0]
  65. screen.cursor_up ()
  66. def DoUp (fsm):
  67. count = int(fsm.memory.pop())
  68. screen = fsm.memory[0]
  69. screen.cursor_up (count)
  70. def DoHome (fsm):
  71. c = int(fsm.memory.pop())
  72. r = int(fsm.memory.pop())
  73. screen = fsm.memory[0]
  74. screen.cursor_home (r,c)
  75. def DoHomeOrigin (fsm):
  76. c = 1
  77. r = 1
  78. screen = fsm.memory[0]
  79. screen.cursor_home (r,c)
  80. def DoEraseDown (fsm):
  81. screen = fsm.memory[0]
  82. screen.erase_down()
  83. def DoErase (fsm):
  84. arg = int(fsm.memory.pop())
  85. screen = fsm.memory[0]
  86. if arg == 0:
  87. screen.erase_down()
  88. elif arg == 1:
  89. screen.erase_up()
  90. elif arg == 2:
  91. screen.erase_screen()
  92. def DoEraseEndOfLine (fsm):
  93. screen = fsm.memory[0]
  94. screen.erase_end_of_line()
  95. def DoEraseLine (fsm):
  96. arg = int(fsm.memory.pop())
  97. screen = fsm.memory[0]
  98. if arg == 0:
  99. screen.erase_end_of_line()
  100. elif arg == 1:
  101. screen.erase_start_of_line()
  102. elif arg == 2:
  103. screen.erase_line()
  104. def DoEnableScroll (fsm):
  105. screen = fsm.memory[0]
  106. screen.scroll_screen()
  107. def DoCursorSave (fsm):
  108. screen = fsm.memory[0]
  109. screen.cursor_save_attrs()
  110. def DoCursorRestore (fsm):
  111. screen = fsm.memory[0]
  112. screen.cursor_restore_attrs()
  113. def DoScrollRegion (fsm):
  114. screen = fsm.memory[0]
  115. r2 = int(fsm.memory.pop())
  116. r1 = int(fsm.memory.pop())
  117. screen.scroll_screen_rows (r1,r2)
  118. def DoMode (fsm):
  119. screen = fsm.memory[0]
  120. mode = fsm.memory.pop() # Should be 4
  121. # screen.setReplaceMode ()
  122. def DoLog (fsm):
  123. screen = fsm.memory[0]
  124. fsm.memory = [screen]
  125. fout = open ('log', 'a')
  126. fout.write (fsm.input_symbol + ',' + fsm.current_state + '\n')
  127. fout.close()
  128. class term (screen.screen):
  129. """This class is an abstract, generic terminal.
  130. This does nothing. This is a placeholder that
  131. provides a common base class for other terminals
  132. such as an ANSI terminal. """
  133. def __init__ (self, r=24, c=80):
  134. screen.screen.__init__(self, r,c)
  135. class ANSI (term):
  136. """This class implements an ANSI (VT100) terminal.
  137. It is a stream filter that recognizes ANSI terminal
  138. escape sequences and maintains the state of a screen object. """
  139. def __init__ (self, r=24,c=80):
  140. term.__init__(self,r,c)
  141. #self.screen = screen (24,80)
  142. self.state = FSM.FSM ('INIT',[self])
  143. self.state.set_default_transition (DoLog, 'INIT')
  144. self.state.add_transition_any ('INIT', DoEmit, 'INIT')
  145. self.state.add_transition ('\x1b', 'INIT', None, 'ESC')
  146. self.state.add_transition_any ('ESC', DoLog, 'INIT')
  147. self.state.add_transition ('(', 'ESC', None, 'G0SCS')
  148. self.state.add_transition (')', 'ESC', None, 'G1SCS')
  149. self.state.add_transition_list ('AB012', 'G0SCS', None, 'INIT')
  150. self.state.add_transition_list ('AB012', 'G1SCS', None, 'INIT')
  151. self.state.add_transition ('7', 'ESC', DoCursorSave, 'INIT')
  152. self.state.add_transition ('8', 'ESC', DoCursorRestore, 'INIT')
  153. self.state.add_transition ('M', 'ESC', DoUpReverse, 'INIT')
  154. self.state.add_transition ('>', 'ESC', DoUpReverse, 'INIT')
  155. self.state.add_transition ('<', 'ESC', DoUpReverse, 'INIT')
  156. self.state.add_transition ('=', 'ESC', None, 'INIT') # Selects application keypad.
  157. self.state.add_transition ('#', 'ESC', None, 'GRAPHICS_POUND')
  158. self.state.add_transition_any ('GRAPHICS_POUND', None, 'INIT')
  159. self.state.add_transition ('[', 'ESC', None, 'ELB')
  160. # ELB means Escape Left Bracket. That is ^[[
  161. self.state.add_transition ('H', 'ELB', DoHomeOrigin, 'INIT')
  162. self.state.add_transition ('D', 'ELB', DoBackOne, 'INIT')
  163. self.state.add_transition ('B', 'ELB', DoDownOne, 'INIT')
  164. self.state.add_transition ('C', 'ELB', DoForwardOne, 'INIT')
  165. self.state.add_transition ('A', 'ELB', DoUpOne, 'INIT')
  166. self.state.add_transition ('J', 'ELB', DoEraseDown, 'INIT')
  167. self.state.add_transition ('K', 'ELB', DoEraseEndOfLine, 'INIT')
  168. self.state.add_transition ('r', 'ELB', DoEnableScroll, 'INIT')
  169. self.state.add_transition ('m', 'ELB', None, 'INIT')
  170. self.state.add_transition ('?', 'ELB', None, 'MODECRAP')
  171. self.state.add_transition_list (string.digits, 'ELB', DoStartNumber, 'NUMBER_1')
  172. self.state.add_transition_list (string.digits, 'NUMBER_1', DoBuildNumber, 'NUMBER_1')
  173. self.state.add_transition ('D', 'NUMBER_1', DoBack, 'INIT')
  174. self.state.add_transition ('B', 'NUMBER_1', DoDown, 'INIT')
  175. self.state.add_transition ('C', 'NUMBER_1', DoForward, 'INIT')
  176. self.state.add_transition ('A', 'NUMBER_1', DoUp, 'INIT')
  177. self.state.add_transition ('J', 'NUMBER_1', DoErase, 'INIT')
  178. self.state.add_transition ('K', 'NUMBER_1', DoEraseLine, 'INIT')
  179. self.state.add_transition ('l', 'NUMBER_1', DoMode, 'INIT')
  180. ### It gets worse... the 'm' code can have infinite number of
  181. ### number;number;number before it. I've never seen more than two,
  182. ### but the specs say it's allowed. crap!
  183. self.state.add_transition ('m', 'NUMBER_1', None, 'INIT')
  184. ### LED control. Same implementation problem as 'm' code.
  185. self.state.add_transition ('q', 'NUMBER_1', None, 'INIT')
  186. # \E[?47h switch to alternate screen
  187. # \E[?47l restores to normal screen from alternate screen.
  188. self.state.add_transition_list (string.digits, 'MODECRAP', DoStartNumber, 'MODECRAP_NUM')
  189. self.state.add_transition_list (string.digits, 'MODECRAP_NUM', DoBuildNumber, 'MODECRAP_NUM')
  190. self.state.add_transition ('l', 'MODECRAP_NUM', None, 'INIT')
  191. self.state.add_transition ('h', 'MODECRAP_NUM', None, 'INIT')
  192. #RM Reset Mode Esc [ Ps l none
  193. self.state.add_transition (';', 'NUMBER_1', None, 'SEMICOLON')
  194. self.state.add_transition_any ('SEMICOLON', DoLog, 'INIT')
  195. self.state.add_transition_list (string.digits, 'SEMICOLON', DoStartNumber, 'NUMBER_2')
  196. self.state.add_transition_list (string.digits, 'NUMBER_2', DoBuildNumber, 'NUMBER_2')
  197. self.state.add_transition_any ('NUMBER_2', DoLog, 'INIT')
  198. self.state.add_transition ('H', 'NUMBER_2', DoHome, 'INIT')
  199. self.state.add_transition ('f', 'NUMBER_2', DoHome, 'INIT')
  200. self.state.add_transition ('r', 'NUMBER_2', DoScrollRegion, 'INIT')
  201. ### It gets worse... the 'm' code can have infinite number of
  202. ### number;number;number before it. I've never seen more than two,
  203. ### but the specs say it's allowed. crap!
  204. self.state.add_transition ('m', 'NUMBER_2', None, 'INIT')
  205. ### LED control. Same problem as 'm' code.
  206. self.state.add_transition ('q', 'NUMBER_2', None, 'INIT')
  207. self.state.add_transition (';', 'NUMBER_2', None, 'SEMICOLON_X')
  208. # Create a state for 'q' and 'm' which allows an infinite number of ignored numbers
  209. self.state.add_transition_any ('SEMICOLON_X', DoLog, 'INIT')
  210. self.state.add_transition_list (string.digits, 'SEMICOLON_X', None, 'NUMBER_X')
  211. self.state.add_transition_any ('NUMBER_X', DoLog, 'INIT')
  212. self.state.add_transition ('m', 'NUMBER_X', None, 'INIT')
  213. self.state.add_transition ('q', 'NUMBER_X', None, 'INIT')
  214. self.state.add_transition (';', 'NUMBER_2', None, 'SEMICOLON_X')
  215. def process (self, c):
  216. self.state.process(c)
  217. def process_list (self, l):
  218. self.write(l)
  219. def write (self, s):
  220. for c in s:
  221. self.process(c)
  222. def flush (self):
  223. pass
  224. def write_ch (self, ch):
  225. """This puts a character at the current cursor position. The cursor
  226. position is moved forward with wrap-around, but no scrolling is done if
  227. the cursor hits the lower-right corner of the screen. """
  228. #\r and \n both produce a call to cr() and lf(), respectively.
  229. ch = ch[0]
  230. if ch == '\r':
  231. self.cr()
  232. return
  233. if ch == '\n':
  234. self.crlf()
  235. return
  236. if ch == chr(screen.BS):
  237. self.cursor_back()
  238. return
  239. if ch not in string.printable:
  240. fout = open ('log', 'a')
  241. fout.write ('Nonprint: ' + str(ord(ch)) + '\n')
  242. fout.close()
  243. return
  244. self.put_abs(self.cur_r, self.cur_c, ch)
  245. old_r = self.cur_r
  246. old_c = self.cur_c
  247. self.cursor_forward()
  248. if old_c == self.cur_c:
  249. self.cursor_down()
  250. if old_r != self.cur_r:
  251. self.cursor_home (self.cur_r, 1)
  252. else:
  253. self.scroll_up ()
  254. self.cursor_home (self.cur_r, 1)
  255. self.erase_line()
  256. # def test (self):
  257. #
  258. # import sys
  259. # write_text = 'I\'ve got a ferret sticking up my nose.\n' + \
  260. # '(He\'s got a ferret sticking up his nose.)\n' + \
  261. # 'How it got there I can\'t tell\n' + \
  262. # 'But now it\'s there it hurts like hell\n' + \
  263. # 'And what is more it radically affects my sense of smell.\n' + \
  264. # '(His sense of smell.)\n' + \
  265. # 'I can see a bare-bottomed mandril.\n' + \
  266. # '(Slyly eyeing his other nostril.)\n' + \
  267. # 'If it jumps inside there too I really don\'t know what to do\n' + \
  268. # 'I\'ll be the proud posessor of a kind of nasal zoo.\n' + \
  269. # '(A nasal zoo.)\n' + \
  270. # 'I\'ve got a ferret sticking up my nose.\n' + \
  271. # '(And what is worst of all it constantly explodes.)\n' + \
  272. # '"Ferrets don\'t explode," you say\n' + \
  273. # 'But it happened nine times yesterday\n' + \
  274. # 'And I should know for each time I was standing in the way.\n' + \
  275. # 'I\'ve got a ferret sticking up my nose.\n' + \
  276. # '(He\'s got a ferret sticking up his nose.)\n' + \
  277. # 'How it got there I can\'t tell\n' + \
  278. # 'But now it\'s there it hurts like hell\n' + \
  279. # 'And what is more it radically affects my sense of smell.\n' + \
  280. # '(His sense of smell.)'
  281. # self.fill('.')
  282. # self.cursor_home()
  283. # for c in write_text:
  284. # self.write_ch (c)
  285. # print str(self)
  286. #
  287. #if __name__ == '__main__':
  288. # t = ANSI(6,65)
  289. # t.test()