from __future__ import unicode_literals from prompt_toolkit.cache import FastDictCache from prompt_toolkit.token import Token from prompt_toolkit.utils import get_cwidth from collections import defaultdict, namedtuple __all__ = ( 'Point', 'Size', 'Screen', 'Char', ) Point = namedtuple('Point', 'y x') Size = namedtuple('Size', 'rows columns') class Char(object): """ Represent a single character in a :class:`.Screen`. This should be considered immutable. """ __slots__ = ('char', 'token', 'width') # If we end up having one of these special control sequences in the input string, # we should display them as follows: # Usually this happens after a "quoted insert". display_mappings = { '\x00': '^@', # Control space '\x01': '^A', '\x02': '^B', '\x03': '^C', '\x04': '^D', '\x05': '^E', '\x06': '^F', '\x07': '^G', '\x08': '^H', '\x09': '^I', '\x0a': '^J', '\x0b': '^K', '\x0c': '^L', '\x0d': '^M', '\x0e': '^N', '\x0f': '^O', '\x10': '^P', '\x11': '^Q', '\x12': '^R', '\x13': '^S', '\x14': '^T', '\x15': '^U', '\x16': '^V', '\x17': '^W', '\x18': '^X', '\x19': '^Y', '\x1a': '^Z', '\x1b': '^[', # Escape '\x1c': '^\\', '\x1d': '^]', '\x1f': '^_', '\x7f': '^?', # Backspace } def __init__(self, char=' ', token=Token): # If this character has to be displayed otherwise, take that one. char = self.display_mappings.get(char, char) self.char = char self.token = token # Calculate width. (We always need this, so better to store it directly # as a member for performance.) self.width = get_cwidth(char) def __eq__(self, other): return self.char == other.char and self.token == other.token def __ne__(self, other): # Not equal: We don't do `not char.__eq__` here, because of the # performance of calling yet another function. return self.char != other.char or self.token != other.token def __repr__(self): return '%s(%r, %r)' % (self.__class__.__name__, self.char, self.token) _CHAR_CACHE = FastDictCache(Char, size=1000 * 1000) Transparent = Token.Transparent class Screen(object): """ Two dimentional buffer of :class:`.Char` instances. """ def __init__(self, default_char=None, initial_width=0, initial_height=0): if default_char is None: default_char = _CHAR_CACHE[' ', Transparent] self.data_buffer = defaultdict(lambda: defaultdict(lambda: default_char)) #: Escape sequences to be injected. self.zero_width_escapes = defaultdict(lambda: defaultdict(lambda: '')) #: Position of the cursor. self.cursor_position = Point(y=0, x=0) #: Visibility of the cursor. self.show_cursor = True #: (Optional) Where to position the menu. E.g. at the start of a completion. #: (We can't use the cursor position, because we don't want the #: completion menu to change its position when we browse through all the #: completions.) self.menu_position = None #: Currently used width/height of the screen. This will increase when #: data is written to the screen. self.width = initial_width or 0 self.height = initial_height or 0 def replace_all_tokens(self, token): """ For all the characters in the screen. Set the token to the given `token`. """ b = self.data_buffer for y, row in b.items(): for x, char in row.items(): b[y][x] = _CHAR_CACHE[char.char, token] class WritePosition(object): def __init__(self, xpos, ypos, width, height, extended_height=None): assert height >= 0 assert extended_height is None or extended_height >= 0 assert width >= 0 # xpos and ypos can be negative. (A float can be partially visible.) self.xpos = xpos self.ypos = ypos self.width = width self.height = height self.extended_height = extended_height or height def __repr__(self): return '%s(%r, %r, %r, %r, %r)' % ( self.__class__.__name__, self.xpos, self.ypos, self.width, self.height, self.extended_height)