def test_substyles(): style = Style([ ("a.b", "#ff0000 bold"), ("a", "#0000ff"), ("b", "#00ff00"), ("b.c", "#0000ff italic"), ]) # Starting with a.* expected = Attrs( color="0000ff", bgcolor="", bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False, ) assert style.get_attrs_for_style_str("class:a") == expected expected = Attrs( color="ff0000", bgcolor="", bold=True, underline=False, italic=False, blink=False, reverse=False, hidden=False, ) assert style.get_attrs_for_style_str("class:a.b") == expected assert style.get_attrs_for_style_str("class:a.b.c") == expected # Starting with b.* expected = Attrs( color="00ff00", bgcolor="", bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False, ) assert style.get_attrs_for_style_str("class:b") == expected assert style.get_attrs_for_style_str("class:b.a") == expected expected = Attrs( color="0000ff", bgcolor="", bold=False, underline=False, italic=True, blink=False, reverse=False, hidden=False, ) assert style.get_attrs_for_style_str("class:b.c") == expected assert style.get_attrs_for_style_str("class:b.c.d") == expected
def __init__(self, fileno, lexer=None, name='<stdin>'): assert isinstance(fileno, int) assert lexer is None or isinstance(lexer, Lexer) assert isinstance(name, six.text_type) self.fileno = fileno self.lexer = lexer self.name = name self._line_tokens = [] self._eof = False # Default style attributes. self._attrs = Attrs(color=None, bgcolor=None, bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False) # Start input parser. self._parser = self._parse_corot() next(self._parser) self._stdin_reader = PosixStdinReader(fileno)
def test_class_combinations_2(): # In this case, our style has both class 'a' and 'b'. # The style that is defined the latest get priority. style = Style([ ("a b", "#ff0000"), ("b", "#00ff00"), ("a", "#0000ff"), ]) expected = Attrs( color="00ff00", bgcolor="", bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False, ) assert style.get_attrs_for_style_str("class:a class:b") == expected assert style.get_attrs_for_style_str("class:a,b") == expected assert style.get_attrs_for_style_str("class:a,b,c") == expected # Defining 'a' latest should give priority to 'a'. expected = Attrs( color="0000ff", bgcolor="", bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False, ) assert style.get_attrs_for_style_str("class:b class:a") == expected assert style.get_attrs_for_style_str("class:b,a") == expected
def test_class_combinations_2(): # In this case, our style has both class 'a' and 'b'. # The style that is defined the latest get priority. style = Style([ ('a b', '#ff0000'), ('b', '#00ff00'), ('a', '#0000ff'), ]) expected = Attrs(color='00ff00', bgcolor='', bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False) assert style.get_attrs_for_style_str('class:a class:b') == expected assert style.get_attrs_for_style_str('class:a,b') == expected assert style.get_attrs_for_style_str('class:a,b,c') == expected # Defining 'a' latest should give priority to 'a'. expected = Attrs(color='0000ff', bgcolor='', bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False) assert style.get_attrs_for_style_str('class:b class:a') == expected assert style.get_attrs_for_style_str('class:b,a') == expected
def _reset_screen(self): """ Reset the Screen content. (also called when switching from/to alternate buffer. """ self.pt_screen = Screen(default_char=Char( ' ', '')) # TODO: maybe stop using this Screen class. self.pt_screen.cursor_position = CursorPosition(0, 0) self.pt_screen.show_cursor = True self.data_buffer = self.pt_screen.data_buffer self.pt_cursor_position = self.pt_screen.cursor_position self.wrapped_lines = [] # List of line indexes that were wrapped. self._attrs = Attrs(color=None, bgcolor=None, bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False) self._style_str = '' self.margins = None self.max_y = 0 # Max 'y' position to which is written.
def test_style_inheritance(self): style = style_from_dict({ Token: '#ff0000', Token.A.B.C: 'bold', Token.A.B.C.D: '#ansired', Token.A.B.C.D.E: 'noinherit blink' }) expected = Attrs(color='ff0000', bgcolor=None, bold=True, underline=False, italic=False, blink=False, reverse=False) self.assertEqual(style.get_attrs_for_token(Token.A.B.C), expected) expected = Attrs(color='ansired', bgcolor=None, bold=True, underline=False, italic=False, blink=False, reverse=False) self.assertEqual(style.get_attrs_for_token(Token.A.B.C.D), expected) expected = Attrs(color=None, bgcolor=None, bold=False, underline=False, italic=False, blink=True, reverse=False) self.assertEqual(style.get_attrs_for_token(Token.A.B.C.D.E), expected)
def test_substyles(): style = Style([ ('a.b', '#ff0000 bold'), ('a', '#0000ff'), ('b', '#00ff00'), ('b.c', '#0000ff italic'), ]) # Starting with a.* expected = Attrs(color='0000ff', bgcolor='', bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False) assert style.get_attrs_for_style_str('class:a') == expected expected = Attrs(color='ff0000', bgcolor='', bold=True, underline=False, italic=False, blink=False, reverse=False, hidden=False) assert style.get_attrs_for_style_str('class:a.b') == expected assert style.get_attrs_for_style_str('class:a.b.c') == expected # Starting with b.* expected = Attrs(color='00ff00', bgcolor='', bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False) assert style.get_attrs_for_style_str('class:b') == expected assert style.get_attrs_for_style_str('class:b.a') == expected expected = Attrs(color='0000ff', bgcolor='', bold=False, underline=False, italic=True, blink=False, reverse=False, hidden=False) assert style.get_attrs_for_style_str('class:b.c') == expected assert style.get_attrs_for_style_str('class:b.c.d') == expected
def test_style_from_dict(): style = Style.from_dict({ 'a': '#ff0000 bold underline italic', 'b': 'bg:#00ff00 blink reverse', }) # Lookup of class:a. expected = Attrs(color='ff0000', bgcolor='', bold=True, underline=True, italic=True, blink=False, reverse=False, hidden=False) assert style.get_attrs_for_style_str('class:a') == expected # Lookup of class:b. expected = Attrs(color='', bgcolor='00ff00', bold=False, underline=False, italic=False, blink=True, reverse=True, hidden=False) assert style.get_attrs_for_style_str('class:b') == expected # Test inline style. expected = Attrs(color='ff0000', bgcolor='', bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False) assert style.get_attrs_for_style_str('#ff0000') == expected # Combine class name and inline style (Whatever is defined later gets priority.) expected = Attrs(color='00ff00', bgcolor='', bold=True, underline=True, italic=True, blink=False, reverse=False, hidden=False) assert style.get_attrs_for_style_str('class:a #00ff00') == expected expected = Attrs(color='ff0000', bgcolor='', bold=True, underline=True, italic=True, blink=False, reverse=False, hidden=False) assert style.get_attrs_for_style_str('#00ff00 class:a') == expected
def test_swap_light_and_dark_style_transformation(): transformation = SwapLightAndDarkStyleTransformation() # Test with 6 digit hex colors. before = Attrs( color="440000", bgcolor="888844", bold=True, underline=True, strike=True, italic=True, blink=False, reverse=False, hidden=False, ) after = Attrs( color="ffbbbb", bgcolor="bbbb76", bold=True, underline=True, strike=True, italic=True, blink=False, reverse=False, hidden=False, ) assert transformation.transform_attrs(before) == after # Test with ANSI colors. before = Attrs( color="ansired", bgcolor="ansiblack", bold=True, underline=True, strike=True, italic=True, blink=False, reverse=False, hidden=False, ) after = Attrs( color="ansibrightred", bgcolor="ansiwhite", bold=True, underline=True, strike=True, italic=True, blink=False, reverse=False, hidden=False, ) assert transformation.transform_attrs(before) == after
def test_style_from_dict(): style = style_from_dict({ Token.A: '#ff0000 bold underline italic', Token.B: 'bg:#00ff00 blink reverse', }) expected = Attrs(color='ff0000', bgcolor=None, bold=True, underline=True, italic=True, blink=False, reverse=False) assert style.get_attrs_for_token(Token.A) == expected expected = Attrs(color=None, bgcolor='00ff00', bold=False, underline=False, italic=False, blink=True, reverse=True) assert style.get_attrs_for_token(Token.B) == expected
def test_class_combinations_1(): # In this case, our style has both class 'a' and 'b'. # Given that the style for 'a b' is defined at the end, that one is used. style = Style([ ("a", "#0000ff"), ("b", "#00ff00"), ("a b", "#ff0000"), ]) expected = Attrs( color="ff0000", bgcolor="", bold=False, underline=False, strike=False, italic=False, blink=False, reverse=False, hidden=False, ) assert style.get_attrs_for_style_str("class:a class:b") == expected assert style.get_attrs_for_style_str("class:a,b") == expected assert style.get_attrs_for_style_str("class:a,b,c") == expected # Changing the order shouldn't matter. assert style.get_attrs_for_style_str("class:b class:a") == expected assert style.get_attrs_for_style_str("class:b,a") == expected
def get_attrs_for_token(self, token): if ( token not in PowerlinePromptToken or len(token) != len(PowerlinePromptToken) + 1 or not token[-1].startswith('Pl') or token[-1] == 'Pl' ): return super(PowerlinePromptStyle, self).get_attrs_for_token(token) ret = { 'color': None, 'bgcolor': None, 'bold': None, 'underline': None, 'italic': None, 'reverse': False, 'blink': False, } for prop in token[-1][3:].split('_'): if prop[0] == 'a': ret[prop[1:]] = True elif prop[0] == 'f': ret['color'] = prop[1:] elif prop[0] == 'b': ret['bgcolor'] = prop[1:] return Attrs(**ret)
def _get_attrs_for_token(self, token): if token and token[0] == 'C': # Token starts with ('C',). Token describes its own style. return Attrs(*token[1:]) else: # Take styles from UI style. return self._style.get_attrs_for_token(token)
def get_attrs_for_token(self, token): if token and token[0] == 'C': # Token starts with ('C',). Token describes its own style. c, fg, bg, bold, underline, italic, blink, reverse = token return Attrs(fg, bg, bold, underline, italic, blink, reverse) else: # Take styles from UI style. return self.ui_style.get_attrs_for_token(token)
def _reset_screen(self): """ Reset the Screen content. (also called when switching from/to alternate buffer. """ self.pt_screen = Screen(default_char=Char(' ', DEFAULT_TOKEN)) self.pt_screen.cursor_position = CursorPosition(0, 0) self.pt_screen.show_cursor = True self.data_buffer = self.pt_screen.data_buffer self.pt_cursor_position = self.pt_screen.cursor_position self._attrs = Attrs(color=None, bgcolor=None, bold=False, underline=False, italic=False, blink=False, reverse=False) self.margins = None self.max_y = 0 # Max 'y' position to which is written.
def default_attrs(): return Attrs(color='', bgcolor='', bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False)
def test_swap_light_and_dark_style_transformation(): transformation = SwapLightAndDarkStyleTransformation() # Test with 6 digit hex colors. before = Attrs(color='440000', bgcolor='888844', bold=True, underline=True, italic=True, blink=False, reverse=False, hidden=False) after = Attrs(color='ffbbbb', bgcolor='bbbb76', bold=True, underline=True, italic=True, blink=False, reverse=False, hidden=False) assert transformation.transform_attrs(before) == after # Test with ANSI colors. before = Attrs(color='ansired', bgcolor='ansiblack', bold=True, underline=True, italic=True, blink=False, reverse=False, hidden=False) after = Attrs(color='ansibrightred', bgcolor='ansiwhite', bold=True, underline=True, italic=True, blink=False, reverse=False, hidden=False) assert transformation.transform_attrs(before) == after
def _reset_screen(self): """(BetterScreen) -> NoneType Reset the Screen content. (also called when switching from/to alternate buffer. """ self.pt_screen = Screen(default_char=Char(' ', DEFAULT_TOKEN)) self.pt_screen.cursor_position = CursorPosition(0, 0) self.pt_screen.show_cursor = True self.data_buffer = self.pt_screen.data_buffer self._attrs = Attrs(color=None, bgcolor=None, bold=False, underline=False, italic=False, blink=False, reverse=False) self.margins = Margins(0, self.lines - 1) self.line_offset = 0 # Index of the line that's currently displayed on top. self.max_y = 0 # Max 'y' position to which is written.
def get_attrs_for_token(self, token): """(PymuxStyle, type(token)) -> Attributes *Desciption* """ if token and token[0] == 'C': # Token starts with ('C',). Token describes its own style. c, fg, bg, bold, underline, italic, blink, reverse = token return Attrs(fg, bg, bold, underline, italic, blink, reverse) else: # Take styles from Pygments style. return self.pygments_style.get_attrs_for_token(token)
def _get_attributes(output, color: str): attr = Attrs(color=color, bgcolor='', bold=False, underline=False, italic=False, blink=False, reverse=False) if output.true_color() and not output.ansi_colors_only(): return output._escape_code_cache_true_color[attr] else: return output._escape_code_cache[attr]
def __init__( self, fileno: int, lexer: Optional[Lexer] = None, name: str = "<stdin>", encoding: str = "utf-8", ) -> None: self.fileno = fileno self.lexer = lexer self.name = name self._line_tokens: StyleAndTextTuples = [] self._eof = False # Default style attributes. self._attrs = Attrs( color=None, bgcolor=None, bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False, ) # Start input parser. self._parser = self._parse_corot() next(self._parser) # Create incremental decoder for decoding stdin. # We can not just do `os.read(stdin.fileno(), 1024).decode('utf-8')`, # because it could be that we are in the middle of a utf-8 byte # sequence. self._stdin_decoder_cls = getincrementaldecoder(encoding) self._stdin_decoder = self._stdin_decoder_cls(errors="ignore")
def _get_attributes(output, color: str): attr = Attrs(color=color, bgcolor='', bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False) # if output.true_color() and not output.ansi_colors_only(): # return output._escape_code_cache_true_color[attr] # else: # FIXME: probably want this to be configurable escape_code_caches = output._escape_code_caches[ColorDepth.default()] return escape_code_caches[attr]
def _reset_screen(self): """ Reset the Screen content. (also called when switching from/to alternate buffer. """ self.pt_screen = Screen(default_char=Char(' ', DEFAULT_TOKEN)) self.pt_screen.cursor_position = CursorPosition(0, 0) self.pt_screen.show_cursor = True self.data_buffer = self.pt_screen.data_buffer self._attrs = Attrs(color=None, bgcolor=None, bold=False, underline=False, italic=False, blink=False, reverse=False) self.margins = Margins(0, self.lines - 1) self.max_y = 0 # Max 'y' position to which is written.
def test_class_combinations_1(): # In this case, our style has both class 'a' and 'b'. # Given that the style for 'a b' is defined at the end, that one is used. style = Style([ ('a', '#0000ff'), ('b', '#00ff00'), ('a b', '#ff0000'), ]) expected = Attrs(color='ff0000', bgcolor='', bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False) assert style.get_attrs_for_style_str('class:a class:b') == expected assert style.get_attrs_for_style_str('class:a,b') == expected assert style.get_attrs_for_style_str('class:a,b,c') == expected # Changing the order shouldn't matter. assert style.get_attrs_for_style_str('class:b class:a') == expected assert style.get_attrs_for_style_str('class:b,a') == expected
def __init__(self, fileno, lexer=None, name='<stdin>'): assert isinstance(fileno, int) assert lexer is None or isinstance(lexer, Lexer) assert isinstance(name, six.text_type) self.fileno = fileno self.lexer = lexer self.name = name self._line_tokens = [] self._eof = False # Default style attributes. self._attrs = Attrs( color=None, bgcolor=None, bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False) # Start input parser. self._parser = self._parse_corot() next(self._parser) self._stdin_reader = PosixStdinReader(fileno)
def test_print_with_style(monkeypatch): mock = Mock(return_value=None) monkeypatch.setattr(DummyOutput, "write", mock.write) monkeypatch.setattr(DummyOutput, "set_attributes", mock.set_attributes) print_formatted_text("Hello World", style="bold italic fg:darkred", output=DummyOutput()) assert len(mock.method_calls) == 4 assert mock.method_calls[0][0] == "set_attributes" assert mock.method_calls[0][1][0] == Attrs( color="8b0000", bgcolor="", bold=True, underline=False, italic=True, blink=False, reverse=False, hidden=False, ) assert mock.method_calls[1][0] == "write" assert mock.method_calls[1][1][0] == "Hello World"
from pyte.screens import Margins from six.moves import range from prompt_toolkit.cache import FastDictCache from prompt_toolkit.layout.screen import Screen, Char from prompt_toolkit.styles import Attrs from prompt_toolkit.terminal.vt100_output import FG_ANSI_COLORS, BG_ANSI_COLORS from prompt_toolkit.terminal.vt100_output import _256_colors as _256_colors_table from collections import namedtuple __all__ = ( 'BetterScreen', 'DEFAULT_TOKEN', ) DEFAULT_TOKEN = ('C', ) + Attrs(color=None, bgcolor=None, bold=False, underline=False, italic=False, blink=False, reverse=False) class CursorPosition(object): " Mutable CursorPosition. " def __init__(self, x=0, y=0): self.x = x self.y = y def __repr__(self): return 'pymux.CursorPosition(x=%r, y=%r)' % (self.x, self.y) _CHAR_CACHE = FastDictCache(Char, size=1000 * 1000)
class PipeSource(Source): """ When input is read from another process that is chained to use through a unix pipe. """ def __init__( self, fileno: int, lexer: Optional[Lexer] = None, name: str = "<stdin>", encoding: str = "utf-8", ) -> None: self.fileno = fileno self.lexer = lexer self.name = name self._line_tokens: StyleAndTextTuples = [] self._eof = False # Default style attributes. self._attrs = Attrs( color=None, bgcolor=None, bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False, ) # Start input parser. self._parser = self._parse_corot() next(self._parser) # Create incremental decoder for decoding stdin. # We can not just do `os.read(stdin.fileno(), 1024).decode('utf-8')`, # because it could be that we are in the middle of a utf-8 byte # sequence. self._stdin_decoder_cls = getincrementaldecoder(encoding) self._stdin_decoder = self._stdin_decoder_cls(errors="ignore") def get_name(self) -> str: return self.name def eof(self) -> bool: return self._eof def _get_data(self) -> str: data = os.read(self.fileno, 1024) # Nothing more to read, stream is closed. if data == b"": self._eof = True return "" return self._stdin_decoder.decode(data) def read_chunk(self) -> StyleAndTextTuples: # Content is ready for reading on stdin. data = self._get_data() # Send input data to the parser. for c in data: self._parser.send(c) # Return the tokens from the parser. # (Don't return the last token yet, because the parser should # be able to pop if the input starts with \b). if self._eof: tokens = self._line_tokens[:] del self._line_tokens[:] else: tokens = self._line_tokens[:-1] del self._line_tokens[:-1] return tokens def _parse_corot(self) -> Generator[None, str, None]: """ Coroutine that parses the pager input. A \b with any character before should make the next character standout. A \b with an underscore before should make the next character emphasized. """ backspace_style = "" # Style created by backspace characters. line_tokens = self._line_tokens replace_one_token = False while True: csi = False c = yield if c == "\b": # Handle \b escape codes from man pages. if line_tokens: last_char = line_tokens[-1][1] line_tokens.pop() replace_one_token = True if last_char == "_": backspace_style = "class:standout2" else: backspace_style = "class:standout" continue elif c == "\x1b": # Start of color escape sequence. square_bracket = yield if square_bracket == "[": csi = True else: continue elif c == "\x9b": csi = True if csi: # Got a CSI sequence. Color codes are following. current = "" params = [] while True: char = yield if char.isdigit(): current += char else: params.append(min(int(current or 0), 9999)) if char == ";": current = "" elif char == "m": # Set attributes and token. self._select_graphic_rendition( params) ### TODO: use inline style. #### token = ('C', ) + self._attrs break else: # Ignore unspported sequence. break else: line_tokens.append( (self._get_attrs_style() + " " + backspace_style, c)) if replace_one_token: backspace_style = "" def _select_graphic_rendition(self, attrs: Sequence[int]) -> None: """ Taken a list of graphics attributes and apply changes to Attrs. """ # NOTE: This function is almost literally taken from Pymux. # if something is wrong, please report there as well! # https://github.com/jonathanslenders/pymux replace: Dict[str, Union[bool, str, None]] = {} if not attrs: attrs = [0] else: attrs = list(attrs[::-1]) while attrs: attr = attrs.pop() if attr in _fg_colors: replace["color"] = _fg_colors[attr] elif attr in _bg_colors: replace["bgcolor"] = _bg_colors[attr] elif attr == 1: replace["bold"] = True elif attr == 3: replace["italic"] = True elif attr == 4: replace["underline"] = True elif attr == 5: replace["blink"] = True elif attr == 6: replace["blink"] = True # Fast blink. elif attr == 7: replace["reverse"] = True elif attr == 22: replace["bold"] = False elif attr == 23: replace["italic"] = False elif attr == 24: replace["underline"] = False elif attr == 25: replace["blink"] = False elif attr == 27: replace["reverse"] = False elif not attr: replace = {} self._attrs = Attrs( color=None, bgcolor=None, bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False, ) elif attr in (38, 48): n = attrs.pop() # 256 colors. if n == 5: if attr == 38: m = attrs.pop() replace["color"] = _256_colors.get(1024 + m) elif attr == 48: m = attrs.pop() replace["bgcolor"] = _256_colors.get(1024 + m) # True colors. if n == 2: try: color_str = "%02x%02x%02x" % ( attrs.pop(), attrs.pop(), attrs.pop(), ) except IndexError: pass else: if attr == 38: replace["color"] = color_str elif attr == 48: replace["bgcolor"] = color_str self._attrs = self._attrs._replace(**replace) # type: ignore def _get_attrs_style(self) -> str: result = [] attrs = self._attrs if attrs.color: result.append(" fg:{} ".format(attrs.color)) if attrs.bgcolor: result.append(" bg:{} ".format(attrs.bgcolor)) if attrs.bold: result.append(" bold ") if attrs.italic: result.append(" italic ") if attrs.underline: result.append(" underline ") if attrs.blink: result.append(" blink ") if attrs.reverse: result.append(" reverse ") # Recent versions of Groff (used for man pages) use bold and underline # escape sequences rather then backslash-style escape sequences. Apply # the standout/standout2 styles anyway so that we get colored output. # This way, people don't have to set GROFF_NO_SGR=1. if attrs.bold and not attrs.color: result.append(" class:standout ") if attrs.underline and not attrs.color: result.append(" class:standout2 ") return "".join(result)
def transform_attrs(self, attrs: Attrs) -> Attrs: if self.no_ansi: return Attrs("", "", False, False, False, False, False, False, False) return attrs
def test_style_from_dict(): style = Style.from_dict({ "a": "#ff0000 bold underline strike italic", "b": "bg:#00ff00 blink reverse", }) # Lookup of class:a. expected = Attrs( color="ff0000", bgcolor="", bold=True, underline=True, strike=True, italic=True, blink=False, reverse=False, hidden=False, ) assert style.get_attrs_for_style_str("class:a") == expected # Lookup of class:b. expected = Attrs( color="", bgcolor="00ff00", bold=False, underline=False, strike=False, italic=False, blink=True, reverse=True, hidden=False, ) assert style.get_attrs_for_style_str("class:b") == expected # Test inline style. expected = Attrs( color="ff0000", bgcolor="", bold=False, underline=False, strike=False, italic=False, blink=False, reverse=False, hidden=False, ) assert style.get_attrs_for_style_str("#ff0000") == expected # Combine class name and inline style (Whatever is defined later gets priority.) expected = Attrs( color="00ff00", bgcolor="", bold=True, underline=True, strike=True, italic=True, blink=False, reverse=False, hidden=False, ) assert style.get_attrs_for_style_str("class:a #00ff00") == expected expected = Attrs( color="ff0000", bgcolor="", bold=True, underline=True, strike=True, italic=True, blink=False, reverse=False, hidden=False, ) assert style.get_attrs_for_style_str("#00ff00 class:a") == expected
class PipeSource(Source): """ When input is read from another process that is chained to use through a unix pipe. """ def __init__(self, fileno, lexer=None, name='<stdin>'): assert isinstance(fileno, int) assert lexer is None or isinstance(lexer, Lexer) assert isinstance(name, six.text_type) self.fileno = fileno self.lexer = lexer self.name = name self._line_tokens = [] self._eof = False # Default style attributes. self._attrs = Attrs( color=None, bgcolor=None, bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False) # Start input parser. self._parser = self._parse_corot() next(self._parser) self._stdin_reader = PosixStdinReader(fileno) def get_name(self): return self.name def get_fd(self): return self.fileno def eof(self): return self._eof def read_chunk(self): # Content is ready for reading on stdin. data = self._stdin_reader.read() if not data: self._eof = True # Send input data to the parser. for c in data: self._parser.send(c) # Return the tokens from the parser. # (Don't return the last token yet, because the parser should # be able to pop if the input starts with \b). if self._eof: tokens = self._line_tokens[:] del self._line_tokens[:] else: tokens = self._line_tokens[:-1] del self._line_tokens[:-1] return tokens def _parse_corot(self): """ Coroutine that parses the pager input. A \b with any character before should make the next character standout. A \b with an underscore before should make the next character emphasized. """ backspace_style = '' # Style created by backspace characters. line_tokens = self._line_tokens replace_one_token = False while True: csi = False c = yield if c == '\b': # Handle \b escape codes from man pages. if line_tokens: _, last_char = line_tokens[-1] line_tokens.pop() replace_one_token = True if last_char == '_': backspace_style = 'class:standout2' else: backspace_style = 'class:standout' continue elif c == '\x1b': # Start of color escape sequence. square_bracket = yield if square_bracket == '[': csi = True else: continue elif c == '\x9b': csi = True if csi: # Got a CSI sequence. Color codes are following. current = '' params = [] while True: char = yield if char.isdigit(): current += char else: params.append(min(int(current or 0), 9999)) if char == ';': current = '' elif char == 'm': # Set attributes and token. self._select_graphic_rendition(params) ### TODO: use inline style. #### token = ('C', ) + self._attrs break else: # Ignore unspported sequence. break else: line_tokens.append((self._get_attrs_style() + ' ' + backspace_style, c)) if replace_one_token: backspace_style = '' def _select_graphic_rendition(self, attrs): """ Taken a list of graphics attributes and apply changes to Attrs. """ # NOTE: This function is almost literally taken from Pymux. # if something is wrong, please report there as well! # https://github.com/jonathanslenders/pymux replace = {} if not attrs: attrs = [0] else: attrs = list(attrs[::-1]) while attrs: attr = attrs.pop() if attr in _fg_colors: replace["color"] = _fg_colors[attr] elif attr in _bg_colors: replace["bgcolor"] = _bg_colors[attr] elif attr == 1: replace["bold"] = True elif attr == 3: replace["italic"] = True elif attr == 4: replace["underline"] = True elif attr == 5: replace["blink"] = True elif attr == 6: replace["blink"] = True # Fast blink. elif attr == 7: replace["reverse"] = True elif attr == 22: replace["bold"] = False elif attr == 23: replace["italic"] = False elif attr == 24: replace["underline"] = False elif attr == 25: replace["blink"] = False elif attr == 27: replace["reverse"] = False elif not attr: replace = {} self._attrs = Attrs( color=None, bgcolor=None, bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False) elif attr in (38, 48): n = attrs.pop() # 256 colors. if n == 5: if attr == 38: m = attrs.pop() replace["color"] = _256_colors.get(1024 + m) elif attr == 48: m = attrs.pop() replace["bgcolor"] = _256_colors.get(1024 + m) # True colors. if n == 2: try: color_str = '%02x%02x%02x' % (attrs.pop(), attrs.pop(), attrs.pop()) except IndexError: pass else: if attr == 38: replace["color"] = color_str elif attr == 48: replace["bgcolor"] = color_str self._attrs = self._attrs._replace(**replace) def _get_attrs_style(self): result = [] attrs = self._attrs if attrs.color: result.append(' fg:{} '.format(attrs.color)) if attrs.bgcolor: result.append(' bg:{} '.format(attrs.bgcolor)) if attrs.bold: result.append(' bold ') if attrs.italic: result.append(' italic ') if attrs.underline: result.append(' underline ') if attrs.blink: result.append(' blink ') if attrs.reverse: result.append(' reverse ') # Recent versions of Groff (used for man pages) use bold and underline # escape sequences rather then backslash-style escape sequences. Apply # the standout/standout2 styles anyway so that we get colored output. # This way, people don't have to set GROFF_NO_SGR=1. if attrs.bold and not attrs.color: result.append(' class:standout ') if attrs.underline and not attrs.color: result.append(' class:standout2 ') return ''.join(result)
class BetterScreen(object): """ Custom screen class. Most of the methods are called from a vt100 Pyte stream. The data buffer is stored in a :class:`prompt_toolkit.layout.screen.Screen` class, because this way, we can send it to the renderer without any transformation. """ swap_variables = [ 'mode', 'margins', 'charset', 'g0_charset', 'g1_charset', 'tabstops', 'data_buffer', 'pt_cursor_position', 'max_y', ] def __init__(self, lines, columns, write_process_input, bell_func=None, get_history_limit=None): assert isinstance(lines, int) assert isinstance(columns, int) assert callable(write_process_input) assert bell_func is None or callable(bell_func) assert get_history_limit is None or callable(get_history_limit) bell_func = bell_func or (lambda: None) get_history_limit = get_history_limit or (lambda: 2000) self._history_cleanup_counter = 0 self.savepoints = [] self.lines = lines self.columns = columns self.write_process_input = write_process_input self.bell_func = bell_func self.get_history_limit = get_history_limit self.reset() @property def in_application_mode(self): """ True when we are in application mode. This means that the process is expecting some other key sequences as input. (Like for the arrows.) """ # Not in cursor mode. return (1 << 5) in self.mode @property def mouse_support_enabled(self): " True when mouse support has been enabled by the application. " return (1000 << 5) in self.mode @property def urxvt_mouse_support_enabled(self): return (1015 << 5) in self.mode @property def sgr_mouse_support_enabled(self): " Xterm Sgr mouse support. " return (1006 << 5) in self.mode @property def bracketed_paste_enabled(self): return (2004 << 5) in self.mode @property def has_reverse_video(self): " The whole screen is set to reverse video. " return mo.DECSCNM in self.mode def reset(self): """Resets the terminal to its initial state. * Scroll margins are reset to screen boundaries. * Cursor is moved to home location -- ``(0, 0)`` and its attributes are set to defaults (see :attr:`default_char`). * Screen is cleared -- each character is reset to :attr:`default_char`. * Tabstops are reset to "every eight columns". .. note:: Neither VT220 nor VT102 manuals mentioned that terminal modes and tabstops should be reset as well, thanks to :manpage:`xterm` -- we now know that. """ self._reset_screen() self.title = '' self.icon_name = '' # Reset modes. self.mode = set([mo.DECAWM, mo.DECTCEM]) # According to VT220 manual and ``linux/drivers/tty/vt.c`` # the default G0 charset is latin-1, but for reasons unknown # latin-1 breaks ascii-graphics; so G0 defaults to cp437. # XXX: The comment above comes from the original Pyte implementation, # it seems for us that LAT1_MAP should indeed be the default, if # not a French version of Vim would incorrectly show some # characters. self.charset = 0 # self.g0_charset = cs.IBMPC_MAP self.g0_charset = cs.LAT1_MAP self.g1_charset = cs.VT100_MAP # From ``man terminfo`` -- "... hardware tabs are initially # set every `n` spaces when the terminal is powered up. Since # we aim to support VT102 / VT220 and linux -- we use n = 8. # (We choose to create tab stops until x=1000, because we keep the # tab stops when the screen increases in size. The OS X 'ls' command # relies on the stops to be there.) self.tabstops = set(range(8, 1000, 8)) # The original Screen instance, when going to the alternate screen. self._original_screen = None def _reset_screen(self): """ Reset the Screen content. (also called when switching from/to alternate buffer. """ self.pt_screen = Screen(default_char=Char(' ', DEFAULT_TOKEN)) self.pt_screen.cursor_position = CursorPosition(0, 0) self.pt_screen.show_cursor = True self.data_buffer = self.pt_screen.data_buffer self.pt_cursor_position = self.pt_screen.cursor_position self._attrs = Attrs(color=None, bgcolor=None, bold=False, underline=False, italic=False, blink=False, reverse=False) self.margins = None self.max_y = 0 # Max 'y' position to which is written. def resize(self, lines=None, columns=None): # Save the dimensions. lines = lines if lines is not None else self.lines columns = columns if columns is not None else self.columns if self.lines != lines or self.columns != columns: self.lines = lines self.columns = columns self._reset_offset_and_margins() # If the height was reduced, and there are lines below # `cursor_position_y+lines`. Remove them by setting 'max_y'. # (If we don't do this. Clearing the screen, followed by reducing # the height will keep the cursor at the top, hiding some content.) self.max_y = min( self.max_y, self.pt_cursor_position.y + lines - 1) @property def line_offset(self): cpos_y = self.pt_cursor_position.y return max(0, min(cpos_y, self.max_y - self.lines + 1)) def set_margins(self, top=None, bottom=None): """Selects top and bottom margins for the scrolling region. Margins determine which screen lines move during scrolling (see :meth:`index` and :meth:`reverse_index`). Characters added outside the scrolling region do not cause the screen to scroll. :param int top: the smallest line number that is scrolled. :param int bottom: the biggest line number that is scrolled. """ if top is None and bottom is None: return margins = self.margins or Margins(0, self.lines - 1) top = margins.top if top is None else top - 1 bottom = margins.bottom if bottom is None else bottom - 1 # Arguments are 1-based, while :attr:`margins` are zero based -- # so we have to decrement them by one. We also make sure that # both of them is bounded by [0, lines - 1]. top = max(0, min(top, self.lines - 1)) bottom = max(0, min(bottom, self.lines - 1)) # Even though VT102 and VT220 require DECSTBM to ignore regions # of width less than 2, some programs (like aptitude for example) # rely on it. Practicality beats purity. if bottom - top >= 1: self.margins = Margins(top, bottom) # The cursor moves to the home position when the top and # bottom margins of the scrolling region (DECSTBM) changes. self.cursor_position() def _reset_offset_and_margins(self): """ Recalculate offset and move cursor (make sure that the bottom is visible.) """ self.margins = None def set_charset(self, code, mode): """Set active ``G0`` or ``G1`` charset. :param str code: character set code, should be a character from ``"B0UK"`` -- otherwise ignored. :param str mode: if ``"("`` ``G0`` charset is set, if ``")"`` -- we operate on ``G1``. .. warning:: User-defined charsets are currently not supported. """ if code in cs.MAPS: charset_map = cs.MAPS[code] if mode == '(': self.g0_charset = charset_map elif mode == ')': self.g1_charset = charset_map def set_mode(self, *modes, **kwargs): # Private mode codes are shifted, to be distingiushed from non # private ones. if kwargs.get("private"): modes = [mode << 5 for mode in modes] self.mode.update(modes) # When DECOLM mode is set, the screen is erased and the cursor # moves to the home position. if mo.DECCOLM in modes: self.resize(columns=132) self.erase_in_display(2) self.cursor_position() # According to `vttest`, DECOM should also home the cursor, see # vttest/main.c:303. if mo.DECOM in modes: self.cursor_position() # Make the cursor visible. if mo.DECTCEM in modes: self.pt_screen.show_cursor = True # On "\e[?1049h", enter alternate screen mode. Backup the current state, if (1049 << 5) in modes: self._original_screen = self.pt_screen self._original_screen_vars = \ dict((v, getattr(self, v)) for v in self.swap_variables) self._reset_screen() self._reset_offset_and_margins() def reset_mode(self, *modes, **kwargs): """Resets (disables) a given list of modes. :param list modes: modes to reset -- hopefully, each mode is a constant from :mod:`pyte.modes`. """ # Private mode codes are shifted, to be distingiushed from non # private ones. if kwargs.get("private"): modes = [mode << 5 for mode in modes] self.mode.difference_update(modes) # Lines below follow the logic in :meth:`set_mode`. if mo.DECCOLM in modes: self.resize(columns=80) self.erase_in_display(2) self.cursor_position() if mo.DECOM in modes: self.cursor_position() # Hide the cursor. if mo.DECTCEM in modes: self.pt_screen.show_cursor = False # On "\e[?1049l", restore from alternate screen mode. if (1049 << 5) in modes and self._original_screen: for k, v in self._original_screen_vars.items(): setattr(self, k, v) self.pt_screen = self._original_screen self._original_screen = None self._original_screen_vars = {} self._reset_offset_and_margins() @property def _in_alternate_screen(self): return bool(self._original_screen) def shift_in(self): " Activates ``G0`` character set. " self.charset = 0 def shift_out(self): " Activates ``G1`` character set. " self.charset = 1 def draw(self, chars): """ Draw characters. `chars` is supposed to *not* contain any special characters. No newlines or control codes. """ # Aliases for variables that are used more than once in this function. # Local lookups are always faster. # (This draw function is called for every printable character that a # process outputs; it should be as performant as possible.) pt_screen = self.pt_screen data_buffer = pt_screen.data_buffer cursor_position = pt_screen.cursor_position cursor_position_x = cursor_position.x cursor_position_y = cursor_position.y in_irm = mo.IRM in self.mode char_cache = _CHAR_CACHE columns = self.columns # Translating a given character. if self.charset: chars = chars.translate(self.g1_charset) else: chars = chars.translate(self.g0_charset) token = ('C', ) + self._attrs for char in chars: # Create 'Char' instance. pt_char = char_cache[char, token] char_width = pt_char.width # If this was the last column in a line and auto wrap mode is # enabled, move the cursor to the beginning of the next line, # otherwise replace characters already displayed with newly # entered. if cursor_position_x >= columns: if mo.DECAWM in self.mode: self.carriage_return() self.linefeed() cursor_position_x = pt_screen.cursor_position.x cursor_position_y = pt_screen.cursor_position.y else: cursor_position_x -= char_width # If Insert mode is set, new characters move old characters to # the right, otherwise terminal is in Replace mode and new # characters replace old characters at cursor position. if in_irm: self.insert_characters(char_width) row = data_buffer[cursor_position_y] row[cursor_position_x] = pt_char if char_width > 1: row[cursor_position_x + 1] = char_cache[' ', token] elif char_width == 0: # This is probably a part of a decomposed unicode character. # Merge into the previous cell. # See: https://en.wikipedia.org/wiki/Unicode_equivalence prev_char = row[cursor_position_x - 1] row[cursor_position_x - 1] = char_cache[ prev_char.char + char, prev_char.token] # .. note:: We can't use :meth:`cursor_forward()`, because that # way, we'll never know when to linefeed. cursor_position_x += char_width # Update max_y. (Don't use 'max()' for comparing only two values, that # is less efficient.) if cursor_position_y > self.max_y: self.max_y = cursor_position_y cursor_position.x = cursor_position_x def carriage_return(self): " Move the cursor to the beginning of the current line. " self.pt_cursor_position.x = 0 def index(self): """Move the cursor down one line in the same column. If the cursor is at the last line, create a new line at the bottom. """ margins = self.margins # When scrolling over the full screen height -> keep history. if margins is None: # Simply move the cursor one position down. cursor_position = self.pt_cursor_position cursor_position.y += 1 self.max_y = max(self.max_y, cursor_position.y) # Cleanup the history, but only every 100 calls. self._history_cleanup_counter += 1 if self._history_cleanup_counter == 100: self._remove_old_lines_from_history() self._history_cleanup_counter = 0 else: # Move cursor down, but scroll in the scrolling region. top, bottom = self.margins line_offset = self.line_offset if self.pt_cursor_position.y - line_offset == bottom: data_buffer = self.data_buffer for line in range(top, bottom): data_buffer[line + line_offset] = \ data_buffer[line + line_offset + 1] del data_buffer[line + line_offset + 1] else: self.cursor_down() def _remove_old_lines_from_history(self): """ Remove top from the scroll buffer. (Outside bounds of history limit.) """ remove_above = max(0, self.pt_cursor_position.y - self.get_history_limit()) data_buffer = self.pt_screen.data_buffer for line in list(data_buffer): if line < remove_above: del data_buffer[line] def clear_history(self): """ Delete all history from the scroll buffer. """ for line in list(self.data_buffer): if line < self.line_offset: del self.data_buffer[line] def reverse_index(self): margins = self.margins or Margins(0, self.lines - 1) top, bottom = margins line_offset = self.line_offset # When scrolling over the full screen -> keep history. if self.pt_cursor_position.y - line_offset == top: for i in range(bottom - 1, top - 1, -1): self.data_buffer[i + line_offset + 1] = self.data_buffer[i + line_offset] del self.data_buffer[i + line_offset] else: self.cursor_up() def linefeed(self): """Performs an index and, if :data:`~pyte.modes.LNM` is set, a carriage return. """ self.index() if mo.LNM in self.mode: self.carriage_return() def next_line(self): """ When `EscE` has been received. Go to the next line, even when LNM has not been set. """ self.index() self.carriage_return() self.ensure_bounds() def tab(self): """Move to the next tab space, or the end of the screen if there aren't anymore left. """ for stop in sorted(self.tabstops): if self.pt_cursor_position.x < stop: column = stop break else: column = self.columns - 1 self.pt_cursor_position.x = column def backspace(self): """Move cursor to the left one or keep it in it's position if it's at the beginning of the line already. """ self.cursor_back() def save_cursor(self): """Push the current cursor position onto the stack.""" self.savepoints.append(_Savepoint( self.pt_cursor_position.x, self.pt_cursor_position.y, self.g0_charset, self.g1_charset, self.charset, mo.DECOM in self.mode, mo.DECAWM in self.mode, self._attrs)) def restore_cursor(self): """Set the current cursor position to whatever cursor is on top of the stack. """ if self.savepoints: savepoint = self.savepoints.pop() self.g0_charset = savepoint.g0_charset self.g1_charset = savepoint.g1_charset self.charset = savepoint.charset self._attrs = savepoint.attrs if savepoint.origin: self.set_mode(mo.DECOM) if savepoint.wrap: self.set_mode(mo.DECAWM) self.pt_cursor_position.x = savepoint.cursor_x self.pt_cursor_position.y = savepoint.cursor_y self.ensure_bounds(use_margins=True) else: # If nothing was saved, the cursor moves to home position; # origin mode is reset. :todo: DECAWM? self.reset_mode(mo.DECOM) self.cursor_position() def insert_lines(self, count=None): """Inserts the indicated # of lines at line with cursor. Lines displayed **at** and below the cursor move down. Lines moved past the bottom margin are lost. :param count: number of lines to delete. """ count = count or 1 top, bottom = self.margins data_buffer = self.data_buffer line_offset = self.line_offset pt_cursor_position = self.pt_cursor_position # If cursor is outside scrolling margins it -- do nothing. if top <= pt_cursor_position.y - self.line_offset <= bottom: for line in range(bottom, pt_cursor_position.y - line_offset, -1): if line - count < top: try: del data_buffer[line + line_offset] except KeyError: pass else: data_buffer[line + line_offset] = data_buffer[line + line_offset - count] try: del data_buffer[line + line_offset - count] except KeyError: pass self.carriage_return() def delete_lines(self, count=None): """Deletes the indicated # of lines, starting at line with cursor. As lines are deleted, lines displayed below cursor move up. Lines added to bottom of screen have spaces with same character attributes as last line moved up. :param int count: number of lines to delete. """ count = count or 1 top, bottom = self.margins line_offset = self.line_offset pt_cursor_position = self.pt_cursor_position # If cursor is outside scrolling margins it -- do nothin'. if top <= pt_cursor_position.y - line_offset <= bottom: data_buffer = self.data_buffer # Iterate from the cursor Y position until the end of the visible input. for line in range(pt_cursor_position.y - line_offset, bottom + 1): # When 'x' lines further are out of the margins, replace by an empty line, # Otherwise copy the line from there. if line + count > bottom: del data_buffer[line + line_offset] else: data_buffer[line + line_offset] = self.data_buffer[line + count + line_offset] def insert_characters(self, count=None): """Inserts the indicated # of blank characters at the cursor position. The cursor does not move and remains at the beginning of the inserted blank characters. Data on the line is shifted forward. :param int count: number of characters to insert. """ count = count or 1 line = self.data_buffer[self.pt_cursor_position.y] if line: max_columns = max(line.keys()) for i in range(max_columns, self.pt_cursor_position.x - 1, -1): line[i + count] = line[i] del line[i] def delete_characters(self, count=None): count = count or 1 line = self.data_buffer[self.pt_cursor_position.y] if line: max_columns = max(line.keys()) for i in range(self.pt_cursor_position.x, max_columns + 1): line[i] = line[i + count] del line[i + count] def cursor_position(self, line=None, column=None): """Set the cursor to a specific `line` and `column`. Cursor is allowed to move out of the scrolling region only when :data:`~pyte.modes.DECOM` is reset, otherwise -- the position doesn't change. :param int line: line number to move the cursor to. :param int column: column number to move the cursor to. """ column = (column or 1) - 1 line = (line or 1) - 1 # If origin mode (DECOM) is set, line number are relative to # the top scrolling margin. margins = self.margins if margins is not None and mo.DECOM in self.mode: line += margins.top # Cursor is not allowed to move out of the scrolling region. if not (margins.top <= line <= margins.bottom): return self.pt_cursor_position.x = column self.pt_cursor_position.y = line + self.line_offset self.ensure_bounds() def cursor_to_column(self, column=None): """Moves cursor to a specific column in the current line. :param int column: column number to move the cursor to. """ self.pt_cursor_position.x = (column or 1) - 1 self.ensure_bounds() def cursor_to_line(self, line=None): """Moves cursor to a specific line in the current column. :param int line: line number to move the cursor to. """ self.pt_cursor_position.y = (line or 1) - 1 + self.line_offset # If origin mode (DECOM) is set, line number are relative to # the top scrolling margin. margins = self.margins if mo.DECOM in self.mode and margins is not None: self.pt_cursor_position.y += margins.top # FIXME: should we also restrict the cursor to the scrolling # region? self.ensure_bounds() def bell(self, *args): " Bell " self.bell_func() def cursor_down(self, count=None): """Moves cursor down the indicated # of lines in same column. Cursor stops at bottom margin. :param int count: number of lines to skip. """ cursor_position = self.pt_cursor_position margins = self.margins or Margins(0, self.lines - 1) # Ensure bounds. # (Following code is faster than calling `self.ensure_bounds`.) _, bottom = margins cursor_position.y = min(cursor_position.y + (count or 1), bottom + self.line_offset + 1) self.max_y = max(self.max_y, cursor_position.y) def cursor_down1(self, count=None): """Moves cursor down the indicated # of lines to column 1. Cursor stops at bottom margin. :param int count: number of lines to skip. """ self.cursor_down(count) self.carriage_return() def cursor_up(self, count=None): """Moves cursor up the indicated # of lines in same column. Cursor stops at top margin. :param int count: number of lines to skip. """ self.pt_cursor_position.y -= count or 1 self.ensure_bounds(use_margins=True) def cursor_up1(self, count=None): """Moves cursor up the indicated # of lines to column 1. Cursor stops at bottom margin. :param int count: number of lines to skip. """ self.cursor_up(count) self.carriage_return() def cursor_back(self, count=None): """Moves cursor left the indicated # of columns. Cursor stops at left margin. :param int count: number of columns to skip. """ self.pt_cursor_position.x = max( 0, self.pt_cursor_position.x - (count or 1)) self.ensure_bounds() def cursor_forward(self, count=None): """Moves cursor right the indicated # of columns. Cursor stops at right margin. :param int count: number of columns to skip. """ self.pt_cursor_position.x += count or 1 self.ensure_bounds() def erase_characters(self, count=None): """Erases the indicated # of characters, starting with the character at cursor position. Character attributes are set cursor attributes. The cursor remains in the same position. :param int count: number of characters to erase. .. warning:: Even though *ALL* of the VTXXX manuals state that character attributes **should be reset to defaults**, ``libvte``, ``xterm`` and ``ROTE`` completely ignore this. Same applies too all ``erase_*()`` and ``delete_*()`` methods. """ count = count or 1 cursor_position = self.pt_cursor_position row = self.data_buffer[cursor_position.y] for column in range(cursor_position.x, min(cursor_position.x + count, self.columns)): row[column] = Char(token=row[column].token) def erase_in_line(self, type_of=0, private=False): """Erases a line in a specific way. :param int type_of: defines the way the line should be erased in: * ``0`` -- Erases from cursor to end of line, including cursor position. * ``1`` -- Erases from beginning of line to cursor, including cursor position. * ``2`` -- Erases complete line. :param bool private: when ``True`` character attributes aren left unchanged **not implemented**. """ data_buffer = self.data_buffer pt_cursor_position = self.pt_cursor_position if type_of == 2: # Delete line completely. del data_buffer[pt_cursor_position.y] else: line = data_buffer[pt_cursor_position.y] def should_we_delete(column): # TODO: check for off-by-one errors! if type_of == 0: return column >= pt_cursor_position.x if type_of == 1: return column <= pt_cursor_position.x for column in list(line.keys()): if should_we_delete(column): del line[column] def erase_in_display(self, type_of=0, private=False): """Erases display in a specific way. :param int type_of: defines the way the line should be erased in: * ``0`` -- Erases from cursor to end of screen, including cursor position. * ``1`` -- Erases from beginning of screen to cursor, including cursor position. * ``2`` -- Erases complete display. All lines are erased and changed to single-width. Cursor does not move. * ``3`` -- Erase saved lines. (Xterm) Clears the history. :param bool private: when ``True`` character attributes aren left unchanged **not implemented**. """ line_offset = self.line_offset pt_cursor_position = self.pt_cursor_position if type_of == 3: # Clear data buffer. for y in list(self.data_buffer): del self.data_buffer[y] # Reset line_offset. pt_cursor_position.y = 0 self.max_y = 0 else: try: interval = ( # a) erase from cursor to the end of the display, including # the cursor, range(pt_cursor_position.y + 1, line_offset + self.lines), # b) erase from the beginning of the display to the cursor, # including it, range(line_offset, pt_cursor_position.y), # c) erase the whole display. range(line_offset, line_offset + self.lines) )[type_of] except IndexError: return data_buffer = self.data_buffer for line in interval: data_buffer[line] = defaultdict(lambda: Char(' ')) # In case of 0 or 1 we have to erase the line with the cursor. if type_of in [0, 1]: self.erase_in_line(type_of) def set_tab_stop(self): " Set a horizontal tab stop at cursor position. " self.tabstops.add(self.pt_cursor_position.x) def clear_tab_stop(self, type_of=None): """Clears a horizontal tab stop in a specific way, depending on the ``type_of`` value: * ``0`` or nothing -- Clears a horizontal tab stop at cursor position. * ``3`` -- Clears all horizontal tab stops. """ if not type_of: # Clears a horizontal tab stop at cursor position, if it's # present, or silently fails if otherwise. self.tabstops.discard(self.pt_cursor_position.x) elif type_of == 3: self.tabstops = set() # Clears all horizontal tab stops. def ensure_bounds(self, use_margins=None): """Ensure that current cursor position is within screen bounds. :param bool use_margins: when ``True`` or when :data:`~pyte.modes.DECOM` is set, cursor is bounded by top and and bottom margins, instead of ``[0; lines - 1]``. """ margins = self.margins if margins and use_margins or mo.DECOM in self.mode: top, bottom = margins else: top, bottom = 0, self.lines - 1 cursor_position = self.pt_cursor_position line_offset = self.line_offset cursor_position.x = min(max(0, cursor_position.x), self.columns - 1) cursor_position.y = min(max(top + line_offset, cursor_position.y), bottom + line_offset + 1) def alignment_display(self): for y in range(0, self.lines): line = self.data_buffer[y + self.line_offset] for x in range(0, self.columns): line[x] = Char('E') # Mapping of the ANSI color codes to their names. _fg_colors = dict((v, k) for k, v in FG_ANSI_COLORS.items()) _bg_colors = dict((v, k) for k, v in BG_ANSI_COLORS.items()) # Mapping of the escape codes for 256colors to their 'ffffff' value. _256_colors = {} for i, (r, g, b) in enumerate(_256_colors_table.colors): _256_colors[1024 + i] = '%02x%02x%02x' % (r, g, b) def select_graphic_rendition(self, *attrs): """ Support 256 colours """ replace = {} if not attrs: attrs = [0] else: attrs = list(attrs[::-1]) while attrs: attr = attrs.pop() if attr in self._fg_colors: replace["color"] = self._fg_colors[attr] elif attr in self._bg_colors: replace["bgcolor"] = self._bg_colors[attr] elif attr == 1: replace["bold"] = True elif attr == 3: replace["italic"] = True elif attr == 4: replace["underline"] = True elif attr == 5: replace["blink"] = True elif attr == 6: replace["blink"] = True # Fast blink. elif attr == 7: replace["reverse"] = True elif attr == 22: replace["bold"] = False elif attr == 23: replace["italic"] = False elif attr == 24: replace["underline"] = False elif attr == 25: replace["blink"] = False elif attr == 27: replace["reverse"] = False elif not attr: replace = {} self._attrs = Attrs(color=None, bgcolor=None, bold=False, underline=False, italic=False, blink=False, reverse=False) elif attr in (38, 48): n = attrs.pop() # 256 colors. if n == 5: if attr == 38: m = attrs.pop() replace["color"] = self._256_colors.get(1024 + m) elif attr == 48: m = attrs.pop() replace["bgcolor"] = self._256_colors.get(1024 + m) # True colors. if n == 2: try: color_str = '%02x%02x%02x' % (attrs.pop(), attrs.pop(), attrs.pop()) except IndexError: pass else: if attr == 38: replace["color"] = color_str elif attr == 48: replace["bgcolor"] = color_str self._attrs = self._attrs._replace(**replace) def square_close(self, data): # Xterm title / icon name. if data.startswith(('0;', '2;')): self.title = data[2:] elif data.startswith('1;'): self.icon_name = data[2:] def report_device_status(self, data): """ Report cursor position. """ if data == 6: y = self.pt_cursor_position.y - self.line_offset + 1 x = self.pt_cursor_position.x + 1 response = '\x1b[%i;%iR' % (y, x) self.write_process_input(response) def report_device_attributes(self, data): response = '\x1b[>84;0;0c' self.write_process_input(response) def charset_default(self, *a, **kw): " Not implemented. " def charset_utf8(self, *a, **kw): " Not implemented. " def debug(self, *args, **kwargs): pass
def select_graphic_rendition(self, *attrs): """ Support 256 colours """ replace = {} if not attrs: attrs = [0] else: attrs = list(attrs[::-1]) while attrs: attr = attrs.pop() if attr in self._fg_colors: replace["color"] = self._fg_colors[attr] elif attr in self._bg_colors: replace["bgcolor"] = self._bg_colors[attr] elif attr == 1: replace["bold"] = True elif attr == 3: replace["italic"] = True elif attr == 4: replace["underline"] = True elif attr == 5: replace["blink"] = True elif attr == 6: replace["blink"] = True # Fast blink. elif attr == 7: replace["reverse"] = True elif attr == 22: replace["bold"] = False elif attr == 23: replace["italic"] = False elif attr == 24: replace["underline"] = False elif attr == 25: replace["blink"] = False elif attr == 27: replace["reverse"] = False elif not attr: replace = {} self._attrs = Attrs(color=None, bgcolor=None, bold=False, underline=False, italic=False, blink=False, reverse=False) elif attr in (38, 48): n = attrs.pop() # 256 colors. if n == 5: if attr == 38: m = attrs.pop() replace["color"] = self._256_colors.get(1024 + m) elif attr == 48: m = attrs.pop() replace["bgcolor"] = self._256_colors.get(1024 + m) # True colors. if n == 2: try: color_str = '%02x%02x%02x' % (attrs.pop(), attrs.pop(), attrs.pop()) except IndexError: pass else: if attr == 38: replace["color"] = color_str elif attr == 48: replace["bgcolor"] = color_str self._attrs = self._attrs._replace(**replace)
def _select_graphic_rendition(self, attrs): """ Taken a list of graphics attributes and apply changes to Attrs. """ # NOTE: This function is almost literally taken from Pymux. # if something is wrong, please report there as well! # https://github.com/jonathanslenders/pymux replace = {} if not attrs: attrs = [0] else: attrs = list(attrs[::-1]) while attrs: attr = attrs.pop() if attr in _fg_colors: replace["color"] = _fg_colors[attr] elif attr in _bg_colors: replace["bgcolor"] = _bg_colors[attr] elif attr == 1: replace["bold"] = True elif attr == 3: replace["italic"] = True elif attr == 4: replace["underline"] = True elif attr == 5: replace["blink"] = True elif attr == 6: replace["blink"] = True # Fast blink. elif attr == 7: replace["reverse"] = True elif attr == 22: replace["bold"] = False elif attr == 23: replace["italic"] = False elif attr == 24: replace["underline"] = False elif attr == 25: replace["blink"] = False elif attr == 27: replace["reverse"] = False elif not attr: replace = {} self._attrs = Attrs( color=None, bgcolor=None, bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False) elif attr in (38, 48): n = attrs.pop() # 256 colors. if n == 5: if attr == 38: m = attrs.pop() replace["color"] = _256_colors.get(1024 + m) elif attr == 48: m = attrs.pop() replace["bgcolor"] = _256_colors.get(1024 + m) # True colors. if n == 2: try: color_str = '%02x%02x%02x' % (attrs.pop(), attrs.pop(), attrs.pop()) except IndexError: pass else: if attr == 38: replace["color"] = color_str elif attr == 48: replace["bgcolor"] = color_str self._attrs = self._attrs._replace(**replace)