screen.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. from __future__ import unicode_literals
  2. from prompt_toolkit.cache import FastDictCache
  3. from prompt_toolkit.token import Token
  4. from prompt_toolkit.utils import get_cwidth
  5. from collections import defaultdict, namedtuple
  6. __all__ = (
  7. 'Point',
  8. 'Size',
  9. 'Screen',
  10. 'Char',
  11. )
  12. Point = namedtuple('Point', 'y x')
  13. Size = namedtuple('Size', 'rows columns')
  14. class Char(object):
  15. """
  16. Represent a single character in a :class:`.Screen`.
  17. This should be considered immutable.
  18. """
  19. __slots__ = ('char', 'token', 'width')
  20. # If we end up having one of these special control sequences in the input string,
  21. # we should display them as follows:
  22. # Usually this happens after a "quoted insert".
  23. display_mappings = {
  24. '\x00': '^@', # Control space
  25. '\x01': '^A',
  26. '\x02': '^B',
  27. '\x03': '^C',
  28. '\x04': '^D',
  29. '\x05': '^E',
  30. '\x06': '^F',
  31. '\x07': '^G',
  32. '\x08': '^H',
  33. '\x09': '^I',
  34. '\x0a': '^J',
  35. '\x0b': '^K',
  36. '\x0c': '^L',
  37. '\x0d': '^M',
  38. '\x0e': '^N',
  39. '\x0f': '^O',
  40. '\x10': '^P',
  41. '\x11': '^Q',
  42. '\x12': '^R',
  43. '\x13': '^S',
  44. '\x14': '^T',
  45. '\x15': '^U',
  46. '\x16': '^V',
  47. '\x17': '^W',
  48. '\x18': '^X',
  49. '\x19': '^Y',
  50. '\x1a': '^Z',
  51. '\x1b': '^[', # Escape
  52. '\x1c': '^\\',
  53. '\x1d': '^]',
  54. '\x1f': '^_',
  55. '\x7f': '^?', # Backspace
  56. }
  57. def __init__(self, char=' ', token=Token):
  58. # If this character has to be displayed otherwise, take that one.
  59. char = self.display_mappings.get(char, char)
  60. self.char = char
  61. self.token = token
  62. # Calculate width. (We always need this, so better to store it directly
  63. # as a member for performance.)
  64. self.width = get_cwidth(char)
  65. def __eq__(self, other):
  66. return self.char == other.char and self.token == other.token
  67. def __ne__(self, other):
  68. # Not equal: We don't do `not char.__eq__` here, because of the
  69. # performance of calling yet another function.
  70. return self.char != other.char or self.token != other.token
  71. def __repr__(self):
  72. return '%s(%r, %r)' % (self.__class__.__name__, self.char, self.token)
  73. _CHAR_CACHE = FastDictCache(Char, size=1000 * 1000)
  74. Transparent = Token.Transparent
  75. class Screen(object):
  76. """
  77. Two dimentional buffer of :class:`.Char` instances.
  78. """
  79. def __init__(self, default_char=None, initial_width=0, initial_height=0):
  80. if default_char is None:
  81. default_char = _CHAR_CACHE[' ', Transparent]
  82. self.data_buffer = defaultdict(lambda: defaultdict(lambda: default_char))
  83. #: Escape sequences to be injected.
  84. self.zero_width_escapes = defaultdict(lambda: defaultdict(lambda: ''))
  85. #: Position of the cursor.
  86. self.cursor_position = Point(y=0, x=0)
  87. #: Visibility of the cursor.
  88. self.show_cursor = True
  89. #: (Optional) Where to position the menu. E.g. at the start of a completion.
  90. #: (We can't use the cursor position, because we don't want the
  91. #: completion menu to change its position when we browse through all the
  92. #: completions.)
  93. self.menu_position = None
  94. #: Currently used width/height of the screen. This will increase when
  95. #: data is written to the screen.
  96. self.width = initial_width or 0
  97. self.height = initial_height or 0
  98. def replace_all_tokens(self, token):
  99. """
  100. For all the characters in the screen. Set the token to the given `token`.
  101. """
  102. b = self.data_buffer
  103. for y, row in b.items():
  104. for x, char in row.items():
  105. b[y][x] = _CHAR_CACHE[char.char, token]
  106. class WritePosition(object):
  107. def __init__(self, xpos, ypos, width, height, extended_height=None):
  108. assert height >= 0
  109. assert extended_height is None or extended_height >= 0
  110. assert width >= 0
  111. # xpos and ypos can be negative. (A float can be partially visible.)
  112. self.xpos = xpos
  113. self.ypos = ypos
  114. self.width = width
  115. self.height = height
  116. self.extended_height = extended_height or height
  117. def __repr__(self):
  118. return '%s(%r, %r, %r, %r, %r)' % (
  119. self.__class__.__name__,
  120. self.xpos, self.ypos, self.width, self.height, self.extended_height)