PageRenderTime 40ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/lib_pypy/pyrepl/unix_console.py

https://bitbucket.org/nbtaylor/pypy
Python | 577 lines | 527 code | 24 blank | 26 comment | 33 complexity | 3648e61fe77f148aeaf1a00133a2eb77 MD5 | raw file
  1. # Copyright 2000-2010 Michael Hudson-Doyle <micahel@gmail.com>
  2. # Antonio Cuni
  3. # Armin Rigo
  4. #
  5. # All Rights Reserved
  6. #
  7. #
  8. # Permission to use, copy, modify, and distribute this software and
  9. # its documentation for any purpose is hereby granted without fee,
  10. # provided that the above copyright notice appear in all copies and
  11. # that both that copyright notice and this permission notice appear in
  12. # supporting documentation.
  13. #
  14. # THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
  15. # THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  16. # AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
  17. # INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
  18. # RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
  19. # CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  20. # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  21. import termios, select, os, struct, errno
  22. import signal, re, time, sys
  23. from fcntl import ioctl
  24. from pyrepl import curses
  25. from pyrepl.fancy_termios import tcgetattr, tcsetattr
  26. from pyrepl.console import Console, Event
  27. from pyrepl import unix_eventqueue
  28. class InvalidTerminal(RuntimeError):
  29. pass
  30. _error = (termios.error, curses.error, InvalidTerminal)
  31. # there are arguments for changing this to "refresh"
  32. SIGWINCH_EVENT = 'repaint'
  33. FIONREAD = getattr(termios, "FIONREAD", None)
  34. TIOCGWINSZ = getattr(termios, "TIOCGWINSZ", None)
  35. def _my_getstr(cap, optional=0):
  36. r = curses.tigetstr(cap)
  37. if not optional and r is None:
  38. raise InvalidTerminal, \
  39. "terminal doesn't have the required '%s' capability"%cap
  40. return r
  41. # at this point, can we say: AAAAAAAAAAAAAAAAAAAAAARGH!
  42. def maybe_add_baudrate(dict, rate):
  43. name = 'B%d'%rate
  44. if hasattr(termios, name):
  45. dict[getattr(termios, name)] = rate
  46. ratedict = {}
  47. for r in [0, 110, 115200, 1200, 134, 150, 1800, 19200, 200, 230400,
  48. 2400, 300, 38400, 460800, 4800, 50, 57600, 600, 75, 9600]:
  49. maybe_add_baudrate(ratedict, r)
  50. del r, maybe_add_baudrate
  51. delayprog = re.compile("\\$<([0-9]+)((?:/|\\*){0,2})>")
  52. try:
  53. poll = select.poll
  54. except AttributeError:
  55. # this is exactly the minumum necessary to support what we
  56. # do with poll objects
  57. class poll:
  58. def __init__(self):
  59. pass
  60. def register(self, fd, flag):
  61. self.fd = fd
  62. def poll(self, timeout=None):
  63. r,w,e = select.select([self.fd],[],[],timeout)
  64. return r
  65. POLLIN = getattr(select, "POLLIN", None)
  66. class UnixConsole(Console):
  67. def __init__(self, f_in=0, f_out=1, term=None, encoding=None):
  68. if encoding is None:
  69. encoding = sys.getdefaultencoding()
  70. self.encoding = encoding
  71. if isinstance(f_in, int):
  72. self.input_fd = f_in
  73. else:
  74. self.input_fd = f_in.fileno()
  75. if isinstance(f_out, int):
  76. self.output_fd = f_out
  77. else:
  78. self.output_fd = f_out.fileno()
  79. self.pollob = poll()
  80. self.pollob.register(self.input_fd, POLLIN)
  81. curses.setupterm(term, self.output_fd)
  82. self.term = term
  83. self._bel = _my_getstr("bel")
  84. self._civis = _my_getstr("civis", optional=1)
  85. self._clear = _my_getstr("clear")
  86. self._cnorm = _my_getstr("cnorm", optional=1)
  87. self._cub = _my_getstr("cub", optional=1)
  88. self._cub1 = _my_getstr("cub1", 1)
  89. self._cud = _my_getstr("cud", 1)
  90. self._cud1 = _my_getstr("cud1", 1)
  91. self._cuf = _my_getstr("cuf", 1)
  92. self._cuf1 = _my_getstr("cuf1", 1)
  93. self._cup = _my_getstr("cup")
  94. self._cuu = _my_getstr("cuu", 1)
  95. self._cuu1 = _my_getstr("cuu1", 1)
  96. self._dch1 = _my_getstr("dch1", 1)
  97. self._dch = _my_getstr("dch", 1)
  98. self._el = _my_getstr("el")
  99. self._hpa = _my_getstr("hpa", 1)
  100. self._ich = _my_getstr("ich", 1)
  101. self._ich1 = _my_getstr("ich1", 1)
  102. self._ind = _my_getstr("ind", 1)
  103. self._pad = _my_getstr("pad", 1)
  104. self._ri = _my_getstr("ri", 1)
  105. self._rmkx = _my_getstr("rmkx", 1)
  106. self._smkx = _my_getstr("smkx", 1)
  107. ## work out how we're going to sling the cursor around
  108. if 0 and self._hpa: # hpa don't work in windows telnet :-(
  109. self.__move_x = self.__move_x_hpa
  110. elif self._cub and self._cuf:
  111. self.__move_x = self.__move_x_cub_cuf
  112. elif self._cub1 and self._cuf1:
  113. self.__move_x = self.__move_x_cub1_cuf1
  114. else:
  115. raise RuntimeError, "insufficient terminal (horizontal)"
  116. if self._cuu and self._cud:
  117. self.__move_y = self.__move_y_cuu_cud
  118. elif self._cuu1 and self._cud1:
  119. self.__move_y = self.__move_y_cuu1_cud1
  120. else:
  121. raise RuntimeError, "insufficient terminal (vertical)"
  122. if self._dch1:
  123. self.dch1 = self._dch1
  124. elif self._dch:
  125. self.dch1 = curses.tparm(self._dch, 1)
  126. else:
  127. self.dch1 = None
  128. if self._ich1:
  129. self.ich1 = self._ich1
  130. elif self._ich:
  131. self.ich1 = curses.tparm(self._ich, 1)
  132. else:
  133. self.ich1 = None
  134. self.__move = self.__move_short
  135. self.event_queue = unix_eventqueue.EventQueue(self.input_fd)
  136. self.partial_char = ''
  137. self.cursor_visible = 1
  138. def change_encoding(self, encoding):
  139. self.encoding = encoding
  140. def refresh(self, screen, cxy):
  141. # this function is still too long (over 90 lines)
  142. if not self.__gone_tall:
  143. while len(self.screen) < min(len(screen), self.height):
  144. self.__hide_cursor()
  145. self.__move(0, len(self.screen) - 1)
  146. self.__write("\n")
  147. self.__posxy = 0, len(self.screen)
  148. self.screen.append("")
  149. else:
  150. while len(self.screen) < len(screen):
  151. self.screen.append("")
  152. if len(screen) > self.height:
  153. self.__gone_tall = 1
  154. self.__move = self.__move_tall
  155. px, py = self.__posxy
  156. old_offset = offset = self.__offset
  157. height = self.height
  158. if 0:
  159. global counter
  160. try:
  161. counter
  162. except NameError:
  163. counter = 0
  164. self.__write_code(curses.tigetstr("setaf"), counter)
  165. counter += 1
  166. if counter > 8:
  167. counter = 0
  168. # we make sure the cursor is on the screen, and that we're
  169. # using all of the screen if we can
  170. cx, cy = cxy
  171. if cy < offset:
  172. offset = cy
  173. elif cy >= offset + height:
  174. offset = cy - height + 1
  175. elif offset > 0 and len(screen) < offset + height:
  176. offset = max(len(screen) - height, 0)
  177. screen.append("")
  178. oldscr = self.screen[old_offset:old_offset + height]
  179. newscr = screen[offset:offset + height]
  180. # use hardware scrolling if we have it.
  181. if old_offset > offset and self._ri:
  182. self.__hide_cursor()
  183. self.__write_code(self._cup, 0, 0)
  184. self.__posxy = 0, old_offset
  185. for i in range(old_offset - offset):
  186. self.__write_code(self._ri)
  187. oldscr.pop(-1)
  188. oldscr.insert(0, "")
  189. elif old_offset < offset and self._ind:
  190. self.__hide_cursor()
  191. self.__write_code(self._cup, self.height - 1, 0)
  192. self.__posxy = 0, old_offset + self.height - 1
  193. for i in range(offset - old_offset):
  194. self.__write_code(self._ind)
  195. oldscr.pop(0)
  196. oldscr.append("")
  197. self.__offset = offset
  198. for y, oldline, newline, in zip(range(offset, offset + height),
  199. oldscr,
  200. newscr):
  201. if oldline != newline:
  202. self.__write_changed_line(y, oldline, newline, px)
  203. y = len(newscr)
  204. while y < len(oldscr):
  205. self.__hide_cursor()
  206. self.__move(0, y)
  207. self.__posxy = 0, y
  208. self.__write_code(self._el)
  209. y += 1
  210. self.__show_cursor()
  211. self.screen = screen
  212. self.move_cursor(cx, cy)
  213. self.flushoutput()
  214. def __write_changed_line(self, y, oldline, newline, px):
  215. # this is frustrating; there's no reason to test (say)
  216. # self.dch1 inside the loop -- but alternative ways of
  217. # structuring this function are equally painful (I'm trying to
  218. # avoid writing code generators these days...)
  219. x = 0
  220. minlen = min(len(oldline), len(newline))
  221. #
  222. # reuse the oldline as much as possible, but stop as soon as we
  223. # encounter an ESCAPE, because it might be the start of an escape
  224. # sequene
  225. while x < minlen and oldline[x] == newline[x] and newline[x] != '\x1b':
  226. x += 1
  227. if oldline[x:] == newline[x+1:] and self.ich1:
  228. if ( y == self.__posxy[1] and x > self.__posxy[0]
  229. and oldline[px:x] == newline[px+1:x+1] ):
  230. x = px
  231. self.__move(x, y)
  232. self.__write_code(self.ich1)
  233. self.__write(newline[x])
  234. self.__posxy = x + 1, y
  235. elif x < minlen and oldline[x + 1:] == newline[x + 1:]:
  236. self.__move(x, y)
  237. self.__write(newline[x])
  238. self.__posxy = x + 1, y
  239. elif (self.dch1 and self.ich1 and len(newline) == self.width
  240. and x < len(newline) - 2
  241. and newline[x+1:-1] == oldline[x:-2]):
  242. self.__hide_cursor()
  243. self.__move(self.width - 2, y)
  244. self.__posxy = self.width - 2, y
  245. self.__write_code(self.dch1)
  246. self.__move(x, y)
  247. self.__write_code(self.ich1)
  248. self.__write(newline[x])
  249. self.__posxy = x + 1, y
  250. else:
  251. self.__hide_cursor()
  252. self.__move(x, y)
  253. if len(oldline) > len(newline):
  254. self.__write_code(self._el)
  255. self.__write(newline[x:])
  256. self.__posxy = len(newline), y
  257. if '\x1b' in newline:
  258. # ANSI escape characters are present, so we can't assume
  259. # anything about the position of the cursor. Moving the cursor
  260. # to the left margin should work to get to a known position.
  261. self.move_cursor(0, y)
  262. def __write(self, text):
  263. self.__buffer.append((text, 0))
  264. def __write_code(self, fmt, *args):
  265. self.__buffer.append((curses.tparm(fmt, *args), 1))
  266. def __maybe_write_code(self, fmt, *args):
  267. if fmt:
  268. self.__write_code(fmt, *args)
  269. def __move_y_cuu1_cud1(self, y):
  270. dy = y - self.__posxy[1]
  271. if dy > 0:
  272. self.__write_code(dy*self._cud1)
  273. elif dy < 0:
  274. self.__write_code((-dy)*self._cuu1)
  275. def __move_y_cuu_cud(self, y):
  276. dy = y - self.__posxy[1]
  277. if dy > 0:
  278. self.__write_code(self._cud, dy)
  279. elif dy < 0:
  280. self.__write_code(self._cuu, -dy)
  281. def __move_x_hpa(self, x):
  282. if x != self.__posxy[0]:
  283. self.__write_code(self._hpa, x)
  284. def __move_x_cub1_cuf1(self, x):
  285. dx = x - self.__posxy[0]
  286. if dx > 0:
  287. self.__write_code(self._cuf1*dx)
  288. elif dx < 0:
  289. self.__write_code(self._cub1*(-dx))
  290. def __move_x_cub_cuf(self, x):
  291. dx = x - self.__posxy[0]
  292. if dx > 0:
  293. self.__write_code(self._cuf, dx)
  294. elif dx < 0:
  295. self.__write_code(self._cub, -dx)
  296. def __move_short(self, x, y):
  297. self.__move_x(x)
  298. self.__move_y(y)
  299. def __move_tall(self, x, y):
  300. assert 0 <= y - self.__offset < self.height, y - self.__offset
  301. self.__write_code(self._cup, y - self.__offset, x)
  302. def move_cursor(self, x, y):
  303. if y < self.__offset or y >= self.__offset + self.height:
  304. self.event_queue.insert(Event('scroll', None))
  305. else:
  306. self.__move(x, y)
  307. self.__posxy = x, y
  308. self.flushoutput()
  309. def prepare(self):
  310. # per-readline preparations:
  311. self.__svtermstate = tcgetattr(self.input_fd)
  312. raw = self.__svtermstate.copy()
  313. raw.iflag &=~ (termios.BRKINT | termios.INPCK |
  314. termios.ISTRIP | termios.IXON)
  315. raw.oflag &=~ (termios.OPOST)
  316. raw.cflag &=~ (termios.CSIZE|termios.PARENB)
  317. raw.cflag |= (termios.CS8)
  318. raw.lflag &=~ (termios.ICANON|termios.ECHO|
  319. termios.IEXTEN|(termios.ISIG*1))
  320. raw.cc[termios.VMIN] = 1
  321. raw.cc[termios.VTIME] = 0
  322. tcsetattr(self.input_fd, termios.TCSADRAIN, raw)
  323. self.screen = []
  324. self.height, self.width = self.getheightwidth()
  325. self.__buffer = []
  326. self.__posxy = 0, 0
  327. self.__gone_tall = 0
  328. self.__move = self.__move_short
  329. self.__offset = 0
  330. self.__maybe_write_code(self._smkx)
  331. try:
  332. self.old_sigwinch = signal.signal(
  333. signal.SIGWINCH, self.__sigwinch)
  334. except ValueError:
  335. pass
  336. def restore(self):
  337. self.__maybe_write_code(self._rmkx)
  338. self.flushoutput()
  339. tcsetattr(self.input_fd, termios.TCSADRAIN, self.__svtermstate)
  340. if hasattr(self, 'old_sigwinch'):
  341. signal.signal(signal.SIGWINCH, self.old_sigwinch)
  342. def __sigwinch(self, signum, frame):
  343. self.height, self.width = self.getheightwidth()
  344. self.event_queue.insert(Event('resize', None))
  345. def push_char(self, char):
  346. self.partial_char += char
  347. try:
  348. c = unicode(self.partial_char, self.encoding)
  349. except UnicodeError, e:
  350. if len(e.args) > 4 and \
  351. e.args[4] == 'unexpected end of data':
  352. pass
  353. else:
  354. # was: "raise". But it crashes pyrepl, and by extension the
  355. # pypy currently running, in which we are e.g. in the middle
  356. # of some debugging session. Argh. Instead just print an
  357. # error message to stderr and continue running, for now.
  358. self.partial_char = ''
  359. sys.stderr.write('\n%s: %s\n' % (e.__class__.__name__, e))
  360. else:
  361. self.partial_char = ''
  362. self.event_queue.push(c)
  363. def get_event(self, block=1):
  364. while self.event_queue.empty():
  365. while 1: # All hail Unix!
  366. try:
  367. self.push_char(os.read(self.input_fd, 1))
  368. except (IOError, OSError), err:
  369. if err.errno == errno.EINTR:
  370. if not self.event_queue.empty():
  371. return self.event_queue.get()
  372. else:
  373. continue
  374. else:
  375. raise
  376. else:
  377. break
  378. if not block:
  379. break
  380. return self.event_queue.get()
  381. def wait(self):
  382. self.pollob.poll()
  383. def set_cursor_vis(self, vis):
  384. if vis:
  385. self.__show_cursor()
  386. else:
  387. self.__hide_cursor()
  388. def __hide_cursor(self):
  389. if self.cursor_visible:
  390. self.__maybe_write_code(self._civis)
  391. self.cursor_visible = 0
  392. def __show_cursor(self):
  393. if not self.cursor_visible:
  394. self.__maybe_write_code(self._cnorm)
  395. self.cursor_visible = 1
  396. def repaint_prep(self):
  397. if not self.__gone_tall:
  398. self.__posxy = 0, self.__posxy[1]
  399. self.__write("\r")
  400. ns = len(self.screen)*['\000'*self.width]
  401. self.screen = ns
  402. else:
  403. self.__posxy = 0, self.__offset
  404. self.__move(0, self.__offset)
  405. ns = self.height*['\000'*self.width]
  406. self.screen = ns
  407. if TIOCGWINSZ:
  408. def getheightwidth(self):
  409. try:
  410. return int(os.environ["LINES"]), int(os.environ["COLUMNS"])
  411. except KeyError:
  412. height, width = struct.unpack(
  413. "hhhh", ioctl(self.input_fd, TIOCGWINSZ, "\000"*8))[0:2]
  414. if not height: return 25, 80
  415. return height, width
  416. else:
  417. def getheightwidth(self):
  418. try:
  419. return int(os.environ["LINES"]), int(os.environ["COLUMNS"])
  420. except KeyError:
  421. return 25, 80
  422. def forgetinput(self):
  423. termios.tcflush(self.input_fd, termios.TCIFLUSH)
  424. def flushoutput(self):
  425. for text, iscode in self.__buffer:
  426. if iscode:
  427. self.__tputs(text)
  428. else:
  429. os.write(self.output_fd, text.encode(self.encoding, 'replace'))
  430. del self.__buffer[:]
  431. def __tputs(self, fmt, prog=delayprog):
  432. """A Python implementation of the curses tputs function; the
  433. curses one can't really be wrapped in a sane manner.
  434. I have the strong suspicion that this is complexity that
  435. will never do anyone any good."""
  436. # using .get() means that things will blow up
  437. # only if the bps is actually needed (which I'm
  438. # betting is pretty unlkely)
  439. bps = ratedict.get(self.__svtermstate.ospeed)
  440. while 1:
  441. m = prog.search(fmt)
  442. if not m:
  443. os.write(self.output_fd, fmt)
  444. break
  445. x, y = m.span()
  446. os.write(self.output_fd, fmt[:x])
  447. fmt = fmt[y:]
  448. delay = int(m.group(1))
  449. if '*' in m.group(2):
  450. delay *= self.height
  451. if self._pad:
  452. nchars = (bps*delay)/1000
  453. os.write(self.output_fd, self._pad*nchars)
  454. else:
  455. time.sleep(float(delay)/1000.0)
  456. def finish(self):
  457. y = len(self.screen) - 1
  458. while y >= 0 and not self.screen[y]:
  459. y -= 1
  460. self.__move(0, min(y, self.height + self.__offset - 1))
  461. self.__write("\n\r")
  462. self.flushoutput()
  463. def beep(self):
  464. self.__maybe_write_code(self._bel)
  465. self.flushoutput()
  466. if FIONREAD:
  467. def getpending(self):
  468. e = Event('key', '', '')
  469. while not self.event_queue.empty():
  470. e2 = self.event_queue.get()
  471. e.data += e2.data
  472. e.raw += e.raw
  473. amount = struct.unpack(
  474. "i", ioctl(self.input_fd, FIONREAD, "\0\0\0\0"))[0]
  475. raw = unicode(os.read(self.input_fd, amount), self.encoding, 'replace')
  476. e.data += raw
  477. e.raw += raw
  478. return e
  479. else:
  480. def getpending(self):
  481. e = Event('key', '', '')
  482. while not self.event_queue.empty():
  483. e2 = self.event_queue.get()
  484. e.data += e2.data
  485. e.raw += e.raw
  486. amount = 10000
  487. raw = unicode(os.read(self.input_fd, amount), self.encoding, 'replace')
  488. e.data += raw
  489. e.raw += raw
  490. return e
  491. def clear(self):
  492. self.__write_code(self._clear)
  493. self.__gone_tall = 1
  494. self.__move = self.__move_tall
  495. self.__posxy = 0, 0
  496. self.screen = []