Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
    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.
Ejemplo n.º 6
0
    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)
Ejemplo n.º 7
0
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
Ejemplo n.º 8
0
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
Ejemplo n.º 9
0
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
Ejemplo n.º 10
0
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
Ejemplo n.º 11
0
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
Ejemplo n.º 12
0
	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)
Ejemplo n.º 13
0
 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)
Ejemplo n.º 14
0
 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)
Ejemplo n.º 15
0
    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.
Ejemplo n.º 16
0
def default_attrs():
    return Attrs(color='',
                 bgcolor='',
                 bold=False,
                 underline=False,
                 italic=False,
                 blink=False,
                 reverse=False,
                 hidden=False)
Ejemplo n.º 17
0
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
Ejemplo n.º 18
0
    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.
Ejemplo n.º 19
0
 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)
Ejemplo n.º 20
0
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]
Ejemplo n.º 21
0
    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")
Ejemplo n.º 22
0
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]
Ejemplo n.º 23
0
    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.
Ejemplo n.º 24
0
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
Ejemplo n.º 25
0
    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)
Ejemplo n.º 26
0
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"
Ejemplo n.º 27
0
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)

Ejemplo n.º 28
0
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)
Ejemplo n.º 29
0
 def transform_attrs(self, attrs: Attrs) -> Attrs:
     if self.no_ansi:
         return Attrs("", "", False, False, False, False, False, False,
                      False)
     return attrs
Ejemplo n.º 30
0
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
Ejemplo n.º 31
0
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)
Ejemplo n.º 32
0
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
Ejemplo n.º 33
0
    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)
Ejemplo n.º 34
0
    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)