posix_utils.py 3.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. from __future__ import unicode_literals
  2. from codecs import getincrementaldecoder
  3. import os
  4. import six
  5. __all__ = (
  6. 'PosixStdinReader',
  7. )
  8. class PosixStdinReader(object):
  9. """
  10. Wrapper around stdin which reads (nonblocking) the next available 1024
  11. bytes and decodes it.
  12. Note that you can't be sure that the input file is closed if the ``read``
  13. function returns an empty string. When ``errors=ignore`` is passed,
  14. ``read`` can return an empty string if all malformed input was replaced by
  15. an empty string. (We can't block here and wait for more input.) So, because
  16. of that, check the ``closed`` attribute, to be sure that the file has been
  17. closed.
  18. :param stdin_fd: File descriptor from which we read.
  19. :param errors: Can be 'ignore', 'strict' or 'replace'.
  20. On Python3, this can be 'surrogateescape', which is the default.
  21. 'surrogateescape' is preferred, because this allows us to transfer
  22. unrecognised bytes to the key bindings. Some terminals, like lxterminal
  23. and Guake, use the 'Mxx' notation to send mouse events, where each 'x'
  24. can be any possible byte.
  25. """
  26. # By default, we want to 'ignore' errors here. The input stream can be full
  27. # of junk. One occurrence of this that I had was when using iTerm2 on OS X,
  28. # with "Option as Meta" checked (You should choose "Option as +Esc".)
  29. def __init__(self, stdin_fd,
  30. errors=('ignore' if six.PY2 else 'surrogateescape')):
  31. assert isinstance(stdin_fd, int)
  32. self.stdin_fd = stdin_fd
  33. self.errors = errors
  34. # Create incremental decoder for decoding stdin.
  35. # We can not just do `os.read(stdin.fileno(), 1024).decode('utf-8')`, because
  36. # it could be that we are in the middle of a utf-8 byte sequence.
  37. self._stdin_decoder_cls = getincrementaldecoder('utf-8')
  38. self._stdin_decoder = self._stdin_decoder_cls(errors=errors)
  39. #: True when there is nothing anymore to read.
  40. self.closed = False
  41. def read(self, count=1024):
  42. # By default we choose a rather small chunk size, because reading
  43. # big amounts of input at once, causes the event loop to process
  44. # all these key bindings also at once without going back to the
  45. # loop. This will make the application feel unresponsive.
  46. """
  47. Read the input and return it as a string.
  48. Return the text. Note that this can return an empty string, even when
  49. the input stream was not yet closed. This means that something went
  50. wrong during the decoding.
  51. """
  52. if self.closed:
  53. return b''
  54. # Note: the following works better than wrapping `self.stdin` like
  55. # `codecs.getreader('utf-8')(stdin)` and doing `read(1)`.
  56. # Somehow that causes some latency when the escape
  57. # character is pressed. (Especially on combination with the `select`.)
  58. try:
  59. data = os.read(self.stdin_fd, count)
  60. # Nothing more to read, stream is closed.
  61. if data == b'':
  62. self.closed = True
  63. return ''
  64. except OSError:
  65. # In case of SIGWINCH
  66. data = b''
  67. return self._stdin_decoder.decode(data)