Ejemplo n.º 1
0
def test_erase_in_line():
    screen = update(pyte.Screen(5, 5),
                    ["sam i", "s foo", "but a", "re yo", "u?   "],
                    colored=[0])
    screen.cursor_position(1, 3)

    # a) erase from cursor to the end of line
    screen.erase_in_line(0)
    assert (screen.cursor.y, screen.cursor.x) == (0, 2)
    assert screen.display == ["sa   ", "s foo", "but a", "re yo", "u?   "]
    assert screen.buffer[0] == [
        Char("s", fg="red"),
        Char("a", fg="red"), screen.default_char, screen.default_char,
        screen.default_char
    ]

    # b) erase from the beginning of the line to the cursor
    screen = update(screen, ["sam i", "s foo", "but a", "re yo", "u?   "],
                    colored=[0])
    screen.erase_in_line(1)
    assert (screen.cursor.y, screen.cursor.x) == (0, 2)
    assert screen.display == ["    i", "s foo", "but a", "re yo", "u?   "]
    assert screen.buffer[0] == [
        screen.default_char, screen.default_char, screen.default_char,
        Char(" ", fg="red"),
        Char("i", fg="red")
    ]

    # c) erase the entire line
    screen = update(screen, ["sam i", "s foo", "but a", "re yo", "u?   "],
                    colored=[0])
    screen.erase_in_line(2)
    assert (screen.cursor.y, screen.cursor.x) == (0, 2)
    assert screen.display == ["     ", "s foo", "but a", "re yo", "u?   "]
    assert screen.buffer[0] == [screen.default_char] * 5
Ejemplo n.º 2
0
def test_draw_width2_irm():
    screen = pyte.Screen(2, 1)
    screen.draw("コ")
    assert screen.display == ["コ"]
    assert tolist(screen) == [[Char("コ"), Char(" ")]]

    # Overwrite the stub part of a width 2 character.
    screen.set_mode(mo.IRM)
    screen.cursor_to_column(screen.columns)
    screen.draw("x")
    assert screen.display == [" x"]
Ejemplo n.º 3
0
def test_insert_characters():
    screen = update(Screen(3, 3), ["sam", "is ", "foo"], colored=[0])

    # a) normal case
    cursor = copy.copy(screen.cursor)
    screen.insert_characters(2)
    assert (screen.cursor.y, screen.cursor.x) == (cursor.y, cursor.x)
    assert screen[0] == [
        screen.default_char,
        screen.default_char,
        Char("s", fg="red")
    ]

    # b) now inserting from the middle of the line
    screen.cursor.y, screen.cursor.x = 2, 1
    screen.insert_characters(1)
    assert screen[2] == [Char("f"), screen.default_char, Char("o")]

    # c) inserting more than we have
    screen.insert_characters(10)
    assert screen[2] == [Char("f"), screen.default_char, screen.default_char]

    # d) 0 is 1
    screen = update(Screen(3, 3), ["sam", "is ", "foo"], colored=[0])

    screen.cursor_position()
    screen.insert_characters()
    assert screen[0] == [screen.default_char,
                         Char("s", fg="red"), Char("a", fg="red")]

    screen = update(Screen(3, 3), ["sam", "is ", "foo"], colored=[0])
    screen.cursor_position()
    screen.insert_characters(1)
    assert screen[0] == [screen.default_char,
                         Char("s", fg="red"), Char("a", fg="red")]
Ejemplo n.º 4
0
def test_blink():
    screen = pyte.Screen(2, 2)
    assert tolist(screen) == [[screen.default_char, screen.default_char]] * 2
    screen.select_graphic_rendition(5)  # blink.

    screen.draw("f")
    assert tolist(screen) == [[
        Char("f", "default", "default", blink=True), screen.default_char
    ], [screen.default_char, screen.default_char]]
Ejemplo n.º 5
0
def test_erase_character():
    screen = update(pyte.Screen(3, 3), ["sam", "is ", "foo"], colored=[0])

    screen.erase_characters(2)
    assert (screen.cursor.y, screen.cursor.x) == (0, 0)
    assert screen.display == ["  m", "is ", "foo"]
    assert tolist(screen)[0] == [
        screen.default_char,
        screen.default_char,
        Char("m", fg="red")
    ]

    screen.cursor.y, screen.cursor.x = 2, 2
    screen.erase_characters()
    assert (screen.cursor.y, screen.cursor.x) == (2, 2)
    assert screen.display == ["  m", "is ", "fo "]

    screen.cursor.y, screen.cursor.x = 1, 1
    screen.erase_characters(0)
    assert (screen.cursor.y, screen.cursor.x) == (1, 1)
    assert screen.display == ["  m", "i  ", "fo "]

    # ! extreme cases.
    screen = update(pyte.Screen(5, 1), ["12345"], colored=[0])
    screen.cursor.x = 1
    screen.erase_characters(3)
    assert (screen.cursor.y, screen.cursor.x) == (0, 1)
    assert screen.display == ["1   5"]
    assert tolist(screen)[0] == [
        Char("1", fg="red"),
        screen.default_char,
        screen.default_char,
        screen.default_char,
        Char("5", "red")
    ]

    screen = update(pyte.Screen(5, 1), ["12345"], colored=[0])
    screen.cursor.x = 2
    screen.erase_characters(10)
    assert (screen.cursor.y, screen.cursor.x) == (0, 2)
    assert screen.display == ["12   "]
    assert tolist(screen)[0] == [
        Char("1", fg="red"),
        Char("2", fg="red"),
        screen.default_char,
        screen.default_char,
        screen.default_char
    ]

    screen = update(pyte.Screen(5, 1), ["12345"], colored=[0])
    screen.erase_characters(4)
    assert (screen.cursor.y, screen.cursor.x) == (0, 0)
    assert screen.display == ["    5"]
    assert tolist(screen)[0] == [
        screen.default_char,
        screen.default_char,
        screen.default_char,
        screen.default_char,
        Char("5", fg="red")
    ]
Ejemplo n.º 6
0
def update(screen, lines, colored=[]):
    """Updates a given screen object with given lines, colors each line
    from ``colored`` in "red" and returns the modified screen.
    """
    for y, line in enumerate(lines):
        for x, char in enumerate(line):
            if y in colored:
                attrs = {"fg": "red"}
            else:
                attrs = {}
            screen.buffer[y][x] = Char(data=char, **attrs)

    return screen
Ejemplo n.º 7
0
def test_attributes():
    screen = pyte.Screen(2, 2)
    assert screen.buffer == [[screen.default_char, screen.default_char]] * 2
    screen.select_graphic_rendition(1)  # bold.

    # Still default, since we haven't written anything.
    assert screen.buffer == [[screen.default_char, screen.default_char]] * 2
    assert screen.cursor.attrs.bold

    screen.draw("f")
    assert screen.buffer == [[
        Char("f", "default", "default", bold=True), screen.default_char
    ], [screen.default_char, screen.default_char]]
Ejemplo n.º 8
0
def test_erase_in_display():
    screen = update(pyte.Screen(5, 5),
                    ["sam i", "s foo", "but a", "re yo", "u?   "],
                    colored=[2, 3])
    screen.cursor_position(3, 3)

    # a) erase from cursor to the end of the display, including
    #    the cursor
    screen.erase_in_display(0)
    assert (screen.cursor.y, screen.cursor.x) == (2, 2)
    assert screen.display == ["sam i", "s foo", "bu   ", "     ", "     "]
    assert screen.buffer[2:] == [[
        Char("b", fg="red"),
        Char("u", fg="red"), screen.default_char, screen.default_char,
        screen.default_char
    ], [screen.default_char] * 5, [screen.default_char] * 5]

    # b) erase from the beginning of the display to the cursor,
    #    including it
    screen = update(screen, ["sam i", "s foo", "but a", "re yo", "u?   "],
                    colored=[2, 3])
    screen.erase_in_display(1)
    assert (screen.cursor.y, screen.cursor.x) == (2, 2)
    assert screen.display == ["     ", "     ", "    a", "re yo", "u?   "]
    assert screen.buffer[:3] == [
        [screen.default_char] * 5,
        [screen.default_char] * 5,
        [
            screen.default_char, screen.default_char, screen.default_char,
            Char(" ", fg="red"),
            Char("a", fg="red")
        ],
    ]

    # c) erase the while display
    screen.erase_in_display(2)
    assert (screen.cursor.y, screen.cursor.x) == (2, 2)
    assert screen.display == ["     ", "     ", "     ", "     ", "     "]
    assert screen.buffer == [[screen.default_char] * 5] * 5
Ejemplo n.º 9
0
def test_save_cursor():
    # a) cursor position
    screen = pyte.Screen(10, 10)
    screen.save_cursor()
    screen.cursor.x, screen.cursor.y = 3, 5
    screen.save_cursor()
    screen.cursor.x, screen.cursor.y = 4, 4

    screen.restore_cursor()
    assert screen.cursor.x == 3
    assert screen.cursor.y == 5

    screen.restore_cursor()
    assert screen.cursor.x == 0
    assert screen.cursor.y == 0

    # b) modes
    screen = pyte.Screen(10, 10)
    screen.set_mode(mo.DECAWM, mo.DECOM)
    screen.save_cursor()

    screen.reset_mode(mo.DECAWM)

    screen.restore_cursor()
    assert mo.DECAWM in screen.mode
    assert mo.DECOM in screen.mode

    # c) attributes
    screen = pyte.Screen(10, 10)
    screen.select_graphic_rendition(4)
    screen.save_cursor()
    screen.select_graphic_rendition(24)

    assert screen.cursor.attrs == screen.default_char

    screen.restore_cursor()

    assert screen.cursor.attrs != screen.default_char
    assert screen.cursor.attrs == Char(" ", underscore=True)
Ejemplo n.º 10
0
def test_attributes_reset():
    screen = pyte.Screen(2, 2)
    screen.set_mode(mo.LNM)
    assert tolist(screen) == [[screen.default_char, screen.default_char]] * 2
    screen.select_graphic_rendition(1)
    screen.draw("f")
    screen.draw("o")
    screen.draw("o")
    assert tolist(screen) == [
        [Char("f", bold=True), Char("o", bold=True)],
        [Char("o", bold=True), screen.default_char],
    ]

    screen.cursor_position()
    screen.select_graphic_rendition(0)  # Reset
    screen.draw("f")
    assert tolist(screen) == [
        [Char("f"), Char("o", bold=True)],
        [Char("o", bold=True), screen.default_char],
    ]
Ejemplo n.º 11
0
def test_reverse_index():
    screen = update(pyte.Screen(2, 2), ["wo", "ot"], colored=[0])

    # a) reverse indexing on the first row should push rows down
    # and create a new row at the top.
    screen.reverse_index()
    assert (screen.cursor.y, screen.cursor.x) == (0, 0)
    assert tolist(screen) == [
        [screen.default_char, screen.default_char],
        [Char("w", fg="red"), Char("o", fg="red")]
    ]

    # b) once again ...
    screen.reverse_index()
    assert (screen.cursor.y, screen.cursor.x) == (0, 0)
    assert tolist(screen) == [
        [screen.default_char, screen.default_char],
        [screen.default_char, screen.default_char],
    ]

    # c) same with margins
    screen = update(pyte.Screen(2, 5), ["bo", "sh", "th", "er", "oh"],
                    colored=[2, 3])
    screen.set_margins(2, 4)
    screen.cursor.y = 1

    # ... go!
    screen.reverse_index()
    assert (screen.cursor.y, screen.cursor.x) == (1, 0)
    assert screen.display == ["bo", "  ", "sh", "th", "oh"]
    assert tolist(screen) == [
        [Char("b"), Char("o")],
        [screen.default_char, screen.default_char],
        [Char("s"), Char("h")],
        [Char("t", fg="red"), Char("h", fg="red")],
        [Char("o"), Char("h")],
    ]

    # ... and again ...
    screen.reverse_index()
    assert (screen.cursor.y, screen.cursor.x) == (1, 0)
    assert screen.display == ["bo", "  ", "  ", "sh", "oh"]
    assert tolist(screen) == [
        [Char("b"), Char("o")],
        [screen.default_char, screen.default_char],
        [screen.default_char, screen.default_char],
        [Char("s"), Char("h")],
        [Char("o"), Char("h")],
    ]

    # ... and again ...
    screen.reverse_index()
    assert (screen.cursor.y, screen.cursor.x) == (1, 0)
    assert screen.display == ["bo", "  ", "  ", "  ", "oh"]
    assert tolist(screen) == [
        [Char("b"), Char("o")],
        [screen.default_char, screen.default_char],
        [screen.default_char, screen.default_char],
        [screen.default_char, screen.default_char],
        [Char("o"), Char("h")],
    ]

    # look, nothing changes!
    screen.reverse_index()
    assert (screen.cursor.y, screen.cursor.x) == (1, 0)
    assert screen.display == ["bo", "  ", "  ", "  ", "oh"]
    assert tolist(screen) == [
        [Char("b"), Char("o")],
        [screen.default_char, screen.default_char],
        [screen.default_char, screen.default_char],
        [screen.default_char, screen.default_char],
        [Char("o"), Char("h")],
    ]
Ejemplo n.º 12
0
def test_index():
    screen = update(pyte.Screen(2, 2), ["wo", "ot"], colored=[1])

    # a) indexing on a row that isn't the last should just move
    # the cursor down.
    screen.index()
    assert (screen.cursor.y, screen.cursor.x) == (1, 0)
    assert tolist(screen) == [
        [Char("w"), Char("o")],
        [Char("o", fg="red"), Char("t", fg="red")]
    ]

    # b) indexing on the last row should push everything up and
    # create a new row at the bottom.
    screen.index()
    assert screen.cursor.y == 1
    assert tolist(screen) == [
        [Char("o", fg="red"), Char("t", fg="red")],
        [screen.default_char, screen.default_char]
    ]

    # c) same with margins
    screen = update(pyte.Screen(2, 5), ["bo", "sh", "th", "er", "oh"],
                    colored=[1, 2])
    screen.set_margins(2, 4)
    screen.cursor.y = 3

    # ... go!
    screen.index()
    assert (screen.cursor.y, screen.cursor.x) == (3, 0)
    assert screen.display == ["bo", "th", "er", "  ", "oh"]
    assert tolist(screen) == [
        [Char("b"), Char("o", "default")],
        [Char("t", "red"), Char("h", "red")],
        [Char("e"), Char("r")],
        [screen.default_char, screen.default_char],
        [Char("o"), Char("h")],
    ]

    # ... and again ...
    screen.index()
    assert (screen.cursor.y, screen.cursor.x) == (3, 0)
    assert screen.display == ["bo", "er", "  ", "  ", "oh"]
    assert tolist(screen) == [
        [Char("b"), Char("o")],
        [Char("e"), Char("r")],
        [screen.default_char, screen.default_char],
        [screen.default_char, screen.default_char],
        [Char("o"), Char("h")],
    ]

    # ... and again ...
    screen.index()
    assert (screen.cursor.y, screen.cursor.x) == (3, 0)
    assert screen.display == ["bo", "  ", "  ", "  ", "oh"]
    assert tolist(screen) == [
        [Char("b"), Char("o")],
        [screen.default_char, screen.default_char],
        [screen.default_char, screen.default_char],
        [screen.default_char, screen.default_char],
        [Char("o"), Char("h")],
    ]

    # look, nothing changes!
    screen.index()
    assert (screen.cursor.y, screen.cursor.x) == (3, 0)
    assert screen.display == ["bo", "  ", "  ", "  ", "oh"]
    assert tolist(screen) == [
        [Char("b"), Char("o")],
        [screen.default_char, screen.default_char],
        [screen.default_char, screen.default_char],
        [screen.default_char, screen.default_char],
        [Char("o"), Char("h")],
    ]
Ejemplo n.º 13
0
 def default_char(self):
     return Char(data=" ", fg=0)
Ejemplo n.º 14
0
def test_initialize_char():
    # Make sure that char can be correctly initialized with keyword
    # arguments. See #24 on GitHub for details.
    for field in Char._fields[1:]:
        char = Char(field[0], **{field: True})
        assert getattr(char, field)
Ejemplo n.º 15
0
def test_erase_in_display():
    screen = update(pyte.Screen(5, 5),
                    ["sam i",
                     "s foo",
                     "but a",
                     "re yo",
                     "u?   "], colored=[2, 3])
    screen.cursor_position(3, 3)

    # a) erase from cursor to the end of the display, including
    #    the cursor
    screen.erase_in_display(0)
    assert (screen.cursor.y, screen.cursor.x) == (2, 2)
    assert screen.display == ["sam i",
                              "s foo",
                              "bu   ",
                              "     ",
                              "     "]
    assert tolist(screen)[2:] == [
        [Char("b", fg="red"),
         Char("u", fg="red"),
         screen.default_char,
         screen.default_char,
         screen.default_char],
        [screen.default_char] * 5,
        [screen.default_char] * 5
    ]

    # b) erase from the beginning of the display to the cursor,
    #    including it
    screen = update(screen,
                    ["sam i",
                     "s foo",
                     "but a",
                     "re yo",
                     "u?   "], colored=[2, 3])
    screen.erase_in_display(1)
    assert (screen.cursor.y, screen.cursor.x) == (2, 2)
    assert screen.display == ["     ",
                              "     ",
                              "    a",
                              "re yo",
                              "u?   "]
    assert tolist(screen)[:3] == [
        [screen.default_char] * 5,
        [screen.default_char] * 5,
        [screen.default_char,
         screen.default_char,
         screen.default_char,
         Char(" ", fg="red"),
         Char("a", fg="red")],
    ]

    # c) erase the while display
    screen.erase_in_display(2)
    assert (screen.cursor.y, screen.cursor.x) == (2, 2)
    assert screen.display == ["     ",
                              "     ",
                              "     ",
                              "     ",
                              "     "]
    assert tolist(screen) == [[screen.default_char] * 5] * 5

    # d) erase with private mode
    screen = update(pyte.Screen(5, 5),
                    ["sam i",
                     "s foo",
                     "but a",
                     "re yo",
                     "u?   "], colored=[2, 3])
    screen.erase_in_display(3, private=True)
    assert screen.display == ["     ",
                              "     ",
                              "     ",
                              "     ",
                              "     "]

    # e) erase with extra args
    screen = update(pyte.Screen(5, 5),
                    ["sam i",
                     "s foo",
                     "but a",
                     "re yo",
                     "u?   "], colored=[2, 3])
    args = [3, 0]
    screen.erase_in_display(*args)
    assert screen.display == ["     ",
                              "     ",
                              "     ",
                              "     ",
                              "     "]

    # f) erase with extra args and private
    screen = update(pyte.Screen(5, 5),
                    ["sam i",
                     "s foo",
                     "but a",
                     "re yo",
                     "u?   "], colored=[2, 3])
    screen.erase_in_display(*args, private=True)
    assert screen.display == ["     ",
                              "     ",
                              "     ",
                              "     ",
                              "     "]
Ejemplo n.º 16
0
from pyte import graphics as g
from pyte.screens import Char, wcwidth, Margins

from multiplex.ansi import CSI

TERMINATE = "m"

UNDEFINED = object()
RESET_TEXT_ATTRS = set(list(range(1, 10)))
BOLD = 1

empty_meta = Char(
    None,
    fg=None,
    bg=None,
    bold=(),
    italics=None,
    underscore=None,
    strikethrough=None,
    reverse=None,
)

reset = f"{CSI}0{TERMINATE}"
index_to_char_meta = {0: empty_meta}
char_meta_to_index = {empty_meta: 0}
index_to_ansi = {0: reset}

counter = 0


class Screen(pyte.Screen):
    def __init__(self, columns, lines, line_buffer):
Ejemplo n.º 17
0
def test_insert_lines():
    # a) without margins
    screen = update(Screen(3, 3), ["sam", "is ", "foo"], colored=[1])
    screen.insert_lines()

    assert (screen.cursor.y, screen.cursor.x) == (0, 0)
    assert screen.display == ["   ", "sam", "is "]
    assert screen == [
        [screen.default_char] * 3,
        [Char("s"), Char("a"), Char("m")],
        [Char("i", fg="red"), Char("s", fg="red"), Char(" ", fg="red")],
    ]

    screen = update(Screen(3, 3), ["sam", "is ", "foo"], colored=[1])
    screen.insert_lines(2)

    assert (screen.cursor.y, screen.cursor.x) == (0, 0)
    assert screen.display == ["   ", "   ", "sam"]
    assert screen == [
        [screen.default_char] * 3,
        [screen.default_char] * 3,
        [Char("s"), Char("a"), Char("m")]
    ]

    # b) with margins
    screen = update(Screen(3, 5), ["sam", "is ", "foo", "bar", "baz"],
                    colored=[2, 3])
    screen.set_margins(1, 4)
    screen.cursor.y = 1
    screen.insert_lines(1)

    assert (screen.cursor.y, screen.cursor.x) == (1, 0)
    assert screen.display == ["sam", "   ", "is ", "foo", "baz"]
    assert screen == [
        [Char("s"), Char("a"), Char("m")],
        [screen.default_char] * 3,
        [Char("i"), Char("s"), Char(" ")],
        [Char("f", fg="red"), Char("o", fg="red"), Char("o", fg="red")],
        [Char("b"), Char("a"), Char("z")],
    ]

    screen = update(Screen(3, 5), ["sam", "is ", "foo", "bar", "baz"],
                    colored=[2, 3])
    screen.set_margins(1, 3)
    screen.cursor.y = 1
    screen.insert_lines(1)

    assert (screen.cursor.y, screen.cursor.x) == (1, 0)
    assert screen.display == ["sam", "   ", "is ", "bar",  "baz"]
    assert screen == [
        [Char("s"), Char("a"), Char("m")],
        [screen.default_char] * 3,
        [Char("i"), Char("s"), Char(" ")],
        [Char("b", fg="red"), Char("a", fg="red"), Char("r", fg="red")],
        [Char("b"), Char("a"), Char("z")],
    ]

    screen.insert_lines(2)
    assert (screen.cursor.y, screen.cursor.x) == (1, 0)
    assert screen.display == ["sam", "   ", "   ", "bar",  "baz"]
    assert screen == [
        [Char("s"), Char("a"), Char("m")],
        [screen.default_char] * 3,
        [screen.default_char] * 3,
        [Char("b", fg="red"), Char("a", fg="red"), Char("r", fg="red")],
        [Char("b"), Char("a"), Char("z")],
    ]

    # c) with margins -- trying to insert more than we have available
    screen = update(Screen(3, 5), ["sam", "is ", "foo", "bar", "baz"],
                    colored=[2, 3])
    screen.set_margins(2, 4)
    screen.cursor.y = 1
    screen.insert_lines(20)

    assert (screen.cursor.y, screen.cursor.x) == (1, 0)
    assert screen.display == ["sam", "   ", "   ", "   ", "baz"]
    assert screen == [
        [Char("s"), Char("a"), Char("m")],
        [screen.default_char] * 3,
        [screen.default_char] * 3,
        [screen.default_char] * 3,
        [Char("b"), Char("a"), Char("z")],
    ]

    # d) with margins -- trying to insert outside scroll boundaries;
    #    expecting nothing to change
    screen = update(Screen(3, 5), ["sam", "is ", "foo", "bar", "baz"],
                    colored=[2, 3])
    screen.set_margins(2, 4)
    screen.insert_lines(5)

    assert (screen.cursor.y, screen.cursor.x) == (0, 0)
    assert screen.display == ["sam", "is ", "foo", "bar", "baz"]
    assert screen == [
        [Char("s"), Char("a"), Char("m")],
        [Char("i"), Char("s"), Char(" ")],
        [Char("f", fg="red"), Char("o", fg="red"), Char("o", fg="red")],
        [Char("b", fg="red"), Char("a", fg="red"), Char("r", fg="red")],
        [Char("b"), Char("a"), Char("z")],
    ]
Ejemplo n.º 18
0
    def select_graphic_rendition(self, *attrs):
        if not attrs or attrs == (0,):
            self.cursor.attrs = self.default_char
            return

        fg = UNDEFINED
        bg = UNDEFINED
        added_text_attrs = set()
        removed_text_attrs = set()

        attrs = list(reversed(attrs))
        while attrs:
            attr = attrs.pop()
            if attr == 0:
                fg = None
                bg = None
                removed_text_attrs = RESET_TEXT_ATTRS
            elif attr in g.FG_ANSI:
                fg = (attr,)
            elif attr in g.BG:
                bg = (attr,)
            elif attr in g.FG_AIXTERM:
                fg = (attr,)
                added_text_attrs.add(BOLD)
            elif attr in g.BG_AIXTERM:
                bg = (attr,)
                added_text_attrs.add(BOLD)
            elif attr in (g.FG_256, g.BG_256):
                n = attrs.pop()
                if n == 5:
                    value = attr, n, attrs.pop()
                    if attr == g.FG_256:
                        fg = value
                    else:
                        bg = value
                elif n == 2:
                    value = attr, n, attrs.pop(), attrs.pop(), attrs.pop()
                    if attr == g.FG_256:
                        fg = value
                    else:
                        bg = value
            elif 1 <= attr <= 9:
                added_text_attrs.add(attr)
            elif 21 <= attr <= 29:
                removed_text_attrs.add(attr)

        current_meta = index_to_char_meta[self.cursor.attrs.fg]
        current_text_attrs = set(current_meta.bold)
        new_text_attrs = (current_text_attrs | added_text_attrs) - removed_text_attrs

        replace = {}
        if fg is not UNDEFINED:
            replace["fg"] = fg
        if bg is not UNDEFINED:
            replace["bg"] = bg
        replace["bold"] = tuple(sorted(new_text_attrs))
        new_char_meta = current_meta._replace(**replace)

        if new_char_meta in char_meta_to_index:
            index = char_meta_to_index[new_char_meta]
        else:
            global counter
            counter += 1
            index = counter
            char_meta_to_index[new_char_meta] = index
            index_to_char_meta[index] = new_char_meta
            codes = []
            c = new_char_meta
            if c.fg:
                codes.extend(c.fg)
            if c.bg:
                codes.extend(c.bg)
            if c.bold:
                codes.extend(list(c.bold))
            ansi = f'{CSI}{";".join(str(c) for c in codes)}{TERMINATE}'
            index_to_ansi[index] = ansi

        self.cursor.attrs = Char(" ", fg=index)
Ejemplo n.º 19
0
def test_delete_lines():
    # a) without margins
    screen = update(pyte.Screen(3, 3), ["sam", "is ", "foo"], colored=[1])
    screen.delete_lines()

    assert (screen.cursor.y, screen.cursor.x) == (0, 0)
    assert screen.display == ["is ", "foo", "   "]
    assert tolist(screen) == [
        [Char("i", fg="red"), Char("s", fg="red"), Char(" ", fg="red")],
        [Char("f"), Char("o"), Char("o")],
        [screen.default_char] * 3,
    ]

    screen.delete_lines(0)

    assert (screen.cursor.y, screen.cursor.x) == (0, 0)
    assert screen.display == ["foo", "   ", "   "]
    assert tolist(screen) == [
        [Char("f"), Char("o"), Char("o")],
        [screen.default_char] * 3,
        [screen.default_char] * 3,
    ]

    # b) with margins
    screen = update(pyte.Screen(3, 5), ["sam", "is ", "foo", "bar", "baz"],
                    colored=[2, 3])
    screen.set_margins(1, 4)
    screen.cursor.y = 1
    screen.delete_lines(1)

    assert (screen.cursor.y, screen.cursor.x) == (1, 0)
    assert screen.display == ["sam", "foo", "bar", "   ", "baz"]
    assert tolist(screen) == [
        [Char("s"), Char("a"), Char("m")],
        [Char("f", fg="red"), Char("o", fg="red"), Char("o", fg="red")],
        [Char("b", fg="red"), Char("a", fg="red"), Char("r", fg="red")],
        [screen.default_char] * 3,
        [Char("b"), Char("a"), Char("z")],
    ]

    screen = update(pyte.Screen(3, 5), ["sam", "is ", "foo", "bar", "baz"],
                    colored=[2, 3])
    screen.set_margins(1, 4)
    screen.cursor.y = 1
    screen.delete_lines(2)

    assert (screen.cursor.y, screen.cursor.x) == (1, 0)
    assert screen.display == ["sam", "bar", "   ", "   ", "baz"]
    assert tolist(screen) == [
        [Char("s"), Char("a"), Char("m")],
        [Char("b", fg="red"), Char("a", fg="red"), Char("r", fg="red")],
        [screen.default_char] * 3,
        [screen.default_char] * 3,
        [Char("b"), Char("a"), Char("z")],
    ]

    # c) with margins -- trying to delete  more than we have available
    screen = update(pyte.Screen(3, 5),
                    ["sam", "is ", "foo", "bar", "baz"],
                    [None,
                     None,
                     [("red", "default")] * 3,
                     [("red", "default")] * 3,
                     None])
    screen.set_margins(1, 4)
    screen.cursor.y = 1
    screen.delete_lines(5)

    assert (screen.cursor.y, screen.cursor.x) == (1, 0)
    assert screen.display == ["sam", "   ", "   ", "   ", "baz"]
    assert tolist(screen) == [
        [Char("s"), Char("a"), Char("m")],
        [screen.default_char] * 3,
        [screen.default_char] * 3,
        [screen.default_char] * 3,
        [Char("b"), Char("a"), Char("z")],
    ]

    # d) with margins -- trying to delete outside scroll boundaries;
    #    expecting nothing to change
    screen = update(pyte.Screen(3, 5), ["sam", "is ", "foo", "bar", "baz"],
                    colored=[2, 3])
    screen.set_margins(2, 4)
    screen.cursor.y = 0
    screen.delete_lines(5)

    assert (screen.cursor.y, screen.cursor.x) == (0, 0)
    assert screen.display == ["sam", "is ", "foo", "bar", "baz"]
    assert tolist(screen) == [
        [Char("s"), Char("a"), Char("m")],
        [Char("i"), Char("s"), Char(" ")],
        [Char("f", fg="red"), Char("o", fg="red"), Char("o", fg="red")],
        [Char("b", fg="red"), Char("a", fg="red"), Char("r", fg="red")],
        [Char("b"), Char("a"), Char("z")],
    ]
Ejemplo n.º 20
0
class TermScreen(pyte.Screen):
    def __init__(self, columns, lines):
        self.savepoints = []
        # terminal dimensions in characters
        self.lines, self.columns = lines, columns

        self.linecontainer = browserscreen.BrowserScreen()

        # list of drawing events for the cljs screen
        self.events = []

        # current iframe_mode,
        # one of None, 'open' or 'closed'
        # None     .. no active iframe
        # 'open'   .. active iframe which is still being sent data to
        # 'closed' .. active iframe where the initial data has already been sent
        self.iframe_mode = None
        self.iframe_id = None
        self.reset()
        self.events = []

    def _flush_events(self):
        self.events.extend(self.linecontainer.pop_events())

    def pop_events(self):
        self.linecontainer.cursor(self.cursor.y, self.cursor.x)
        self.linecontainer.check_scrollback()
        if self.events:
            self.events.extend(self.linecontainer.pop_events())
            ev = self.events
            self.events = []
            return ev
        else:
            return self.linecontainer.pop_events()

    # pyte.Screen implementation

    def __before__(self, command):
        pass

    def __after__(self, command):
        pass

    _default_char = Char(data=" ", fg="default", bg="default")

    def _create_line(self, default_char=None):
        return Line(self.columns, default_char or self._default_char)

    def _is_empty_line(self, line):
        return line.is_empty()

    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._flush_events()
        self.linecontainer.reset(self.lines)

        if self.iframe_mode:
            self.iframe_leave()

        self.mode = set([mo.DECAWM, mo.DECTCEM, mo.LNM, mo.DECTCEM])
        self.margins = Margins(0, self.lines - 1)

        # 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.
        self.charset = 0
        self.g0_charset = cs.IBMPC_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.
        self.tabstops = set(range(7, self.columns, 8))

        self.cursor = Cursor(0, 0)
        self.cursor_position()

    def resize(self, lines=None, columns=None):
        """Resize the screen to the given dimensions keeping the history intact.

        If the requested screen size has more lines than the existing
        screen, lines will be added at the bottom. If the requested
        size has less lines than the existing screen, lines will be
        clipped at the top of the screen. Similarly, if the existing
        screen has less columns than the requested screen, columns will
        be added at the right, and if it has more -- columns will be
        clipped at the right.

        .. note:: According to `xterm`, we should also reset origin
                  mode and screen margins, see ``xterm/screen.c:1761``.
                  -> but we don't do this here
        :param int lines: number of lines in the new screen.
        :param int columns: number of columns in the new screen.
        """
        self._flush_events()

        old_lines = self.lines

        self.lines = (lines or self.lines)
        self.columns = (columns or self.columns)

        if mo.DECALTBUF in self.mode and False:
            # home cursor
            self.reset_mode(mo.DECOM)
        else:
            # cursor: make sure that it 'stays' on its current line
            cursor_delta = self.linecontainer.resize(old_lines, self.lines,
                                                     self.columns)
            self.cursor.y += cursor_delta
            self.cursor.x = min(max(self.cursor.x, 0), self.columns - 1)

        self.margins = Margins(0, self.lines - 1)

    def set_mode(self, *modes, **kwargs):
        """Sets (enables) a given list of modes.

        :param list modes: modes to set, where each mode is a constant
                           from :mod:`pyte.modes`.
        """
        mode_id = (modes[:1] or [None])[0]
        if mode_id in (IFRAME_DOCUMENT_MODE_ID, IFRAME_RESPONSE_MODE_ID):
            cookie = ';'.join(map(str, modes[1:]))
            # Need the cookie to prove that the request comes from a
            # real program, an not just by 'cat'-ing some file.
            # Javascript in iframes will only be activated with a
            # valid cookie.
            self.iframe_set_mode(mode_id, cookie)
            return

        # Private mode codes are shifted, to be distingiushed from non
        # private ones.
        if kwargs.get("private"):
            modes = set([mode << mo.PRIVATE_MODE_SHIFT for mode in modes])

        # translate mode shortcuts and aliases
        if mo.DECAPPMODE in modes:
            # DECAPP is a combination of DECALTBUF and DECSAVECUR and
            # additionally erase the alternative buffer
            modes.update([mo.DECALTBUF, mo.DECSAVECUR])

        if mo.DECALTBUF_ALT in modes:
            modes.remove(mo.DECALTBUF_ALT)
            modes.add(mo.DECALTBUF)

        self.mode.update(modes)

        if mo.DECAPPKEYS in modes:
            # use application mode keys, see termkey.py (app_key_mode arg)
            pass

        # 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()

        # Mark all displayed characters as reverse.
        if mo.DECSCNM in modes:
            # todo: check that iter(self.linecontainer) lazily creates and returns all lines
            linecontainer.reverse_all_lines()
            self.select_graphic_rendition(g._SGR["+reverse"])

        # Make the cursor visible.
        if mo.DECTCEM in modes:
            self.cursor.hidden = False

        if mo.DECSAVECUR in modes:
            # save cursor position and restore it on mode reset
            self.save_cursor()
            if mo.DECAPPMODE in modes:
                self.cursor_position()

        if mo.DECALTBUF in modes:
            # enable alternative draw buffer
            self._flush_events()
            self.linecontainer.enter_altbuf_mode()

        # if mo.DECAPPMODE in modes:
        #     self.erase_in_display(2)

    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`.
        """

        mode_id = (modes[:1] or [None])[0]
        if mode_id in (IFRAME_DOCUMENT_MODE_ID, IFRAME_RESPONSE_MODE_ID) \
                and kwargs.get('private'):
            cookie = ';'.join(map(str, modes[1:]))
            # Need the cookie to prove that the request comes from a
            # real program, an not just by 'cat'-ing some file.
            # Javascript in iframes will only be activated with a
            # valid cookie.
            self.iframe_reset_mode(mode_id, cookie)
            return

        # Private mode codes aree shifted, to be distingiushed from non
        # private ones.
        if kwargs.get("private"):
            modes = set([mode << mo.PRIVATE_MODE_SHIFT for mode in modes])

        # translate mode shortcuts and aliases
        if mo.DECAPPMODE in modes:
            # DECAPP is a combination of DECALTBUF and DECSAVECUR
            modes.remove(mo.DECAPPMODE)
            modes.update([mo.DECALTBUF, mo.DECSAVECUR])

        if mo.DECALTBUF_ALT in modes:
            modes.remove(mo.DECALTBUF_ALT)
            modes.add(mo.DECALTBUF)

        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()

        if mo.DECSCNM in modes:
            self.linecontainer.reverse_all_lines()
            self.select_graphic_rendition(g._SGR["-reverse"])

        # Hide the cursor.
        if mo.DECTCEM in modes:
            self.cursor.hidden = True

        if mo.DECSAVECUR in modes:
            # save cursor position and restore it on mode reset
            self.restore_cursor()

        if mo.DECALTBUF in modes:
            # disable alternative draw buffer, switch internal
            # linecontainer while preserving generated events
            self._flush_events()
            self.linecontainer.leave_altbuf_mode()

    def draw_string(self, string):
        """Like draw, but for a whole string at once.

        String MUST NOT contain any control characters like newlines or carriage-returns.
        """
        def _write_string(s):
            self.linecontainer.insert_overwrite(self.cursor.y, self.cursor.x,
                                                s, self.cursor.attrs)

        # iframe mode? just write the string
        if self.iframe_mode:
            if self.iframe_mode == 'document':
                self.linecontainer.iframe_write(self.iframe_id, string)
            else:
                # non-string writes to the terminal - useful for frame app debugging
                # TODO: print them within the terminal (popup?, menu?,
                # panel?) to help debugging the application
                print ">", string

        else:
            if mo.IRM in self.mode:
                # move existing chars to the right before inserting string
                # (no wrapping)
                self.insert_characters(len(string))
                _write_string(''.join(
                    reversed(string[-(self.columns - self.cursor.x):])))

            elif mo.DECAWM in self.mode:
                # Auto Wrap Mode
                # all chars up to the end of the current line
                line_end = self.columns - self.cursor.x
                s = string[:line_end]
                _write_string(s)
                self.cursor.x += len(s)
                # remaining chars will be written on subsequent lines
                i = 0
                while len(string) > (line_end + (i * self.columns)):
                    self.linefeed()
                    s = string[line_end + (i * self.columns):line_end +
                               ((i + 1) * self.columns)]
                    _write_string(s)
                    self.cursor.x += len(s)
                    i += 1

            else:
                # no overwrap, just replace the last old char if string
                # will draw over the end of the current line
                line_end = self.columns - self.cursor.x
                if len(string) > line_end:
                    s = string[:line_end - 1] + string[-1]
                else:
                    s = string
                _write_string(s)
                self.cursor.x += len(s)

    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.
        """
        if self.iframe_mode:
            self.linecontainer.iframe_write(self.iframe_id, "\n")
            return

        top, bottom = self.margins
        if self.cursor.y == bottom:
            if top == 0 and bottom == (self.lines - 1):
                # surplus lines move the scrollback if no margin is active
                self.linecontainer.append_line(self.columns)
            else:
                self.linecontainer.insert_line(bottom + 1, self.cursor.attrs)
                # delete surplus lines to achieve scrolling within in the margins
                self.linecontainer.remove_line(top)
        else:
            self.cursor_down()

    def reverse_index(self):
        """Move the cursor up one line in the same column. If the cursor
        is at the first line, create a new line at the top and remove
        the last one, scrolling all lines in between.
        """
        top, bottom = self.margins

        if self.cursor.y == top:
            self.linecontainer.remove_line(bottom)
            self.linecontainer.insert_line(top)
        else:
            self.cursor_up()

    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

        # If cursor is outside scrolling margins it -- do nothin'.
        if top <= self.cursor.y <= bottom:
            # v+1, because range() is exclusive.
            for line in range(self.cursor.y,
                              min(bottom + 1, self.cursor.y + count)):
                self.linecontainer.remove_line(bottom)
                self.linecontainer.insert_line(line, self.cursor.attrs)

            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

        # If cursor is outside scrolling margins it -- do nothin'.
        if top <= self.cursor.y <= bottom:
            #                v -- +1 to include the bottom margin.
            for _ in range(min(bottom - self.cursor.y + 1, count)):
                self.linecontainer.remove_line(self.cursor.y)
                # TODO: get and use the attributes for the *last* line
                self.linecontainer.insert_line(bottom, self.cursor.attrs)

            self.carriage_return()

    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
        self.linecontainer.insert(self.cursor.y, self.cursor.x, ' ' * count,
                                  self.cursor.attrs)

    def delete_characters(self, count=None):
        """Deletes the indicated # of characters, starting with the
        character at cursor position. When a character is deleted, all
        characters to the right of cursor move left. Character attributes
        move with the characters.

        :param int count: number of characters to delete.
        """
        count = count or 1
        # TODO: which style is used for the space characters created
        # on the left? is it really the cursor attrs or is it the
        # style of the rightmost character?
        self.linecontainer.remove(self.cursor.y, self.cursor.x, count)

    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
        self.linecontainer.insert_overwrite(self.cursor.y, self.cursor.x,
                                            ' ' * count, self.cursor.attrs)

    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 are left
                             unchanged **not implemented**.
        """
        if type_of == 0:
            start = self.cursor.x
            end = self.columns
        elif type_of == 1:
            start = 0
            end = self.cursor.x
        else:
            start = 0
            end = self.columns

        self.linecontainer.insert_overwrite(self.cursor.y, start,
                                            ' ' * (end - start),
                                            self.cursor.attrs)

    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.
        :param bool private: when ``True`` character attributes aren left
                             unchanged **not implemented**.
        """
        if type_of in [0, 1]:
            # erase parts of the display -> don't care about history
            interval = (
                # a) erase from cursor to the end of the display, including
                # the cursor,
                range(self.cursor.y + 1, self.lines),
                # b) erase from the beginning of the display to the cursor,
                # including it,
                range(0, self.cursor.y))[type_of]

            s = ' ' * self.columns
            for line in interval:
                # erase the whole line
                self.linecontainer.insert_overwrite(line, 0, s,
                                                    self.cursor.attrs)

            # erase the line with the cursor.
            self.erase_in_line(type_of)

        else:  # type_of == 2
            if mo.DECALTBUF in self.mode:
                s = ' ' * self.columns
                for line in range(self.lines):
                    # erase the whole line
                    self.linecontainer.insert_overwrite(
                        line, 0, s, self.cursor.attrs)
            else:
                # c) erase the whole display ->
                # Push every visible line to the history == add blank
                # lines until all current non-blank lines are above the
                # top of the term window. (thats what xterm does and
                # linux-term not, try using top in both term emulators and
                # see what happens to the history)
                self.linecontainer.add_line_origin(self.lines)

    def string(self, string):
        if self.iframe_mode:
            # in document mode -> 'register resource', debug-message, ...
            # in request mode -> response, send-message, debug-message, ...
            self.linecontainer.iframe_string(self.iframe_id, string)
        else:
            # ignore strings (xterm behaviour) in plain terminal mode
            self.draw_string(string)

    ## xterm title hack

    def os_command(self, string):
        """Parse OS commands.

        Xterm will alter the terminals title according to these commands.
        """
        res = (string or '').split(';', 1)
        if len(res) == 2:
            command_id, data = res
            if command_id == '0':
                self.linecontainer.set_title(data)

    ## iframe extensions

    def _next_iframe_id(self):
        # unique random id to hide the terminals url
        return utils.roll_id()

    def _insert_iframe_line(self):
        self.linecontainer.iframe_enter(self.iframe_id, self.cursor.y)

    def _iframe_close_document(self):
        # add some script to iframes that handles resizing, default key events, ...
        self.linecontainer.iframe_write(self.iframe_id, IFRAME_SCRIPT)
        self.linecontainer.iframe_close(self.iframe_id)

    def iframe_set_mode(self, mode_id, cookie):
        if mode_id == IFRAME_DOCUMENT_MODE_ID:
            # replace the current line with an iframe line at the current
            # cursor position (like self.index())
            # all following chars are written to the iframes root document connection
            if self.iframe_mode == None:
                self.iframe_mode = 'document'
                self.iframe_id = self._next_iframe_id()
                self._insert_iframe_line()
            elif self.iframe_mode == 'response':
                self.iframe_mode = 'document'
            elif self.iframe_mode == 'document':
                pass
            else:
                assert False  # Illegal Iframe Mode

        elif mode_id == IFRAME_RESPONSE_MODE_ID:
            if self.iframe_mode == 'document':
                self.iframe_mode = 'response'
                self._iframe_close_document()
            elif self.iframe_mode == 'response':
                pass
            elif self.iframe_mode == None:
                # TODO: insert the iframe and directly switch into
                # response mode, use '/' as the path for the iframe.
                assert False  # TODO
            else:
                assert False  # unknown iframe mode

        else:
            assert False  # unknown mode_id

    def iframe_reset_mode(self, mode_id, cookie):
        if mode_id in (IFRAME_DOCUMENT_MODE_ID, IFRAME_RESPONSE_MODE_ID):
            # always reset the iframe mode, regardless of the exact mode id
            if self.iframe_mode == 'document':
                self._iframe_close_document()
                self.linecontainer.iframe_leave(self.iframe_id)
                self.iframe_mode = None
            elif self.iframe_mode == 'response':
                self.linecontainer.iframe_leave(self.iframe_id)
                self.iframe_mode = None
            else:
                # not in iframe mode - ignore
                pass
        else:
            assert False  # unknown mode_id

    def close_stream(self):
        # just hand of the event to the linecontainer
        self.linecontainer.close_stream()

    def iframe_resize(self, iframe_id, height):
        self.linecontainer.iframe_resize(iframe_id, height)

    def start_clojurescript_repl(self):
        self.linecontainer.start_clojurescript_repl()