Example #1
0
class TestCurtsiesPainting(FormatStringTest):
    def setUp(self):
        self.repl = Repl(config=setup_config())
        self.repl.rl_history = History()  # clear history
        self.repl.height, self.repl.width = (5, 10)

    def assert_paint(self, screen, cursor_row_col):
        array, cursor_pos = self.repl.paint()
        self.assertFSArraysEqual(array, screen)
        self.assertEqual(cursor_pos, cursor_row_col)

    def assert_paint_ignoring_formatting(self, screen, cursor_row_col):
        array, cursor_pos = self.repl.paint()
        self.assertFSArraysEqualIgnoringFormatting(array, screen)

    def test_startup(self):
        screen = fsarray([cyan('>>> '), cyan('Welcome to')])
        self.assert_paint(screen, (0, 4))

    def test_enter_text(self):
        [self.repl.add_normal_character(c) for c in '1 + 1']
        screen = fsarray([
            cyan('>>> ') + bold(
                green('1') + cyan(' ') + yellow('+') + cyan(' ') + green('1')),
            cyan('Welcome to')
        ])
        self.assert_paint(screen, (0, 9))

    def test_run_line(self):
        try:
            orig_stdout = sys.stdout
            sys.stdout = self.repl.stdout
            [self.repl.add_normal_character(c) for c in '1 + 1']
            self.repl.on_enter()
            screen = fsarray([u'>>> 1 + 1', '2', 'Welcome to'])
            self.assert_paint_ignoring_formatting(screen, (0, 9))
        finally:
            sys.stdout = orig_stdout

    def test_completion(self):
        self.repl.height, self.repl.width = (5, 32)
        self.repl.current_line = 'se'
        self.cursor_offset = 2
        screen = [
            u'>>> se', u'┌───────────────────────┐',
            u'│ set(     setattr(     │', u'└───────────────────────┘', u'',
            u'Welcome to bpython! Press <F1> f'
        ]
        self.assert_paint_ignoring_formatting(screen, (0, 9))
class TestCurtsiesPainting(FormatStringTest):
    def setUp(self):
        self.repl = Repl(config=setup_config())
        self.repl.rl_history = History() # clear history
        self.repl.height, self.repl.width = (5, 10)

    def assert_paint(self, screen, cursor_row_col):
        array, cursor_pos = self.repl.paint()
        self.assertFSArraysEqual(array, screen)
        self.assertEqual(cursor_pos, cursor_row_col)

    def assert_paint_ignoring_formatting(self, screen, cursor_row_col=None):
        array, cursor_pos = self.repl.paint()
        self.assertFSArraysEqualIgnoringFormatting(array, screen)
        if cursor_row_col is not None:
            self.assertEqual(cursor_pos, cursor_row_col)
Example #3
0
class TestCurtsiesPainting(FormatStringTest):
    def setUp(self):
        self.repl = Repl(config=setup_config())
        self.repl.rl_history = History() # clear history
        self.repl.height, self.repl.width = (5, 10)

    def assert_paint(self, screen, cursor_row_col):
        array, cursor_pos = self.repl.paint()
        self.assertFSArraysEqual(array, screen)
        self.assertEqual(cursor_pos, cursor_row_col)

    def assert_paint_ignoring_formatting(self, screen, cursor_row_col=None):
        array, cursor_pos = self.repl.paint()
        self.assertFSArraysEqualIgnoringFormatting(array, screen)
        if cursor_row_col is not None:
            self.assertEqual(cursor_pos, cursor_row_col)
class TestCurtsiesPainting(FormatStringTest):
    def setUp(self):
        self.repl = Repl(config=setup_config())
        self.repl.rl_history = History() # clear history
        self.repl.height, self.repl.width = (5, 10)

    def assert_paint(self, screen, cursor_row_col):
        array, cursor_pos = self.repl.paint()
        self.assertFSArraysEqual(array, screen)
        self.assertEqual(cursor_pos, cursor_row_col)

    def assert_paint_ignoring_formatting(self, screen, cursor_row_col):
        array, cursor_pos = self.repl.paint()
        self.assertFSArraysEqualIgnoringFormatting(array, screen)

    def test_startup(self):
        screen = fsarray([cyan('>>> '), cyan('Welcome to')])
        self.assert_paint(screen, (0, 4))

    def test_enter_text(self):
        [self.repl.add_normal_character(c) for c in '1 + 1']
        screen = fsarray([cyan('>>> ') + bold(green('1')+cyan(' ')+
                          yellow('+') + cyan(' ') + green('1')), cyan('Welcome to')])
        self.assert_paint(screen, (0, 9))

    def test_run_line(self):
        try:
            orig_stdout = sys.stdout
            sys.stdout = self.repl.stdout
            [self.repl.add_normal_character(c) for c in '1 + 1']
            self.repl.on_enter()
            screen = fsarray([u'>>> 1 + 1', '2', 'Welcome to'])
            self.assert_paint_ignoring_formatting(screen, (0, 9))
        finally:
            sys.stdout = orig_stdout

    def test_completion(self):
        self.repl.height, self.repl.width = (5, 32)
        self.repl.current_line = 'se'
        self.cursor_offset = 2
        screen = [u'>>> se',
                  u'┌───────────────────────┐',
                  u'│ set(     setattr(     │',
                  u'└───────────────────────┘',
                  u'',
                  u'Welcome to bpython! Press <F1> f']
        self.assert_paint_ignoring_formatting(screen, (0, 9))
class TestCurtsiesRewindRedraw(CurtsiesPaintingTest):
    def refresh(self):
        self.refresh_requests.append(RefreshRequestEvent())

    def send_refreshes(self):
        while self.refresh_requests:
            self.repl.process_event(self.refresh_requests.pop())
            _, _ = self.repl.paint()

    def enter(self, line=None):
        """Enter a line of text, avoiding autocompletion windows

        autocomplete could still happen if the entered line has
        autocompletion that would happen then, but intermediate
        stages won't happen"""
        if line is not None:
            self.repl._set_cursor_offset(len(line), update_completion=False)
            self.repl.current_line = line
        with output_to_repl(self.repl):
            self.repl.on_enter(insert_into_history=False)
            self.assertEqual(self.repl.rl_history.entries, [''])
            self.send_refreshes()

    def undo(self):
        with output_to_repl(self.repl):
            self.repl.undo()
            self.send_refreshes()

    def setUp(self):
        self.refresh_requests = []
        self.repl = Repl(banner='',
                         config=setup_config(),
                         request_refresh=self.refresh)
        # clear history
        self.repl.rl_history = History()
        self.repl.height, self.repl.width = (5, 32)

    def test_rewind(self):
        self.repl.current_line = '1 + 1'
        self.enter()
        screen = ['>>> 1 + 1', '2', '>>> ']
        self.assert_paint_ignoring_formatting(screen, (2, 4))
        self.repl.undo()
        screen = ['>>> ']
        self.assert_paint_ignoring_formatting(screen, (0, 4))

    def test_rewind_contiguity_loss(self):
        self.enter('1 + 1')
        self.enter('2 + 2')
        self.enter('def foo(x):')
        self.repl.current_line = '    return x + 1'
        screen = [
            '>>> 1 + 1', '2', '>>> 2 + 2', '4', '>>> def foo(x):',
            '...     return x + 1'
        ]
        self.assert_paint_ignoring_formatting(screen, (5, 8))
        self.repl.scroll_offset = 1
        self.assert_paint_ignoring_formatting(screen[1:], (4, 8))
        self.undo()
        screen = ['2', '>>> 2 + 2', '4', '>>> ']
        self.assert_paint_ignoring_formatting(screen, (3, 4))
        self.undo()
        screen = ['2', '>>> ']
        self.assert_paint_ignoring_formatting(screen, (1, 4))
        self.undo()
        screen = [
            CONTIGUITY_BROKEN_MSG[:self.repl.width], '>>> ', '', '', '', ' '
        ]  # TODO why is that there? Necessary?
        self.assert_paint_ignoring_formatting(screen, (1, 4))
        screen = ['>>> ']
        self.assert_paint_ignoring_formatting(screen, (0, 4))

    def test_inconsistent_history_doesnt_happen_if_onscreen(self):
        self.enter('1 + 1')
        screen = ['>>> 1 + 1', '2', '>>> ']
        self.assert_paint_ignoring_formatting(screen, (2, 4))
        self.enter("2 + 2")
        screen = ['>>> 1 + 1', '2', '>>> 2 + 2', '4', '>>> ']
        self.assert_paint_ignoring_formatting(screen, (4, 4))
        self.repl.display_lines[0] = self.repl.display_lines[0] * 2
        self.undo()
        screen = ['>>> 1 + 1', '2', '>>> ']
        self.assert_paint_ignoring_formatting(screen, (2, 4))

    def test_rewind_inconsistent_history(self):
        self.enter('1 + 1')
        self.enter('2 + 2')
        self.enter('3 + 3')
        screen = ['>>> 1 + 1', '2', '>>> 2 + 2', '4', '>>> 3 + 3', '6', '>>> ']
        self.assert_paint_ignoring_formatting(screen, (6, 4))
        self.repl.scroll_offset += len(screen) - self.repl.height
        self.assert_paint_ignoring_formatting(screen[2:], (4, 4))
        self.repl.display_lines[0] = self.repl.display_lines[0] * 2
        self.undo()
        screen = [
            INCONSISTENT_HISTORY_MSG[:self.repl.width], '>>> 2 + 2', '4',
            '>>> ', '', ' '
        ]
        self.assert_paint_ignoring_formatting(screen, (3, 4))
        self.repl.scroll_offset += len(screen) - self.repl.height
        self.assert_paint_ignoring_formatting(screen[1:-2], (2, 4))
        self.assert_paint_ignoring_formatting(screen[1:-2], (2, 4))

    def test_rewind_inconsistent_history_more_lines_same_screen(self):
        self.repl.width = 60
        sys.a = 5
        self.enter('import sys')
        self.enter('for i in range(sys.a):')
        self.enter('    print(sys.a)')
        self.enter('')
        self.enter('1 + 1')
        self.enter('2 + 2')
        screen = [
            '>>> import sys', '>>> for i in range(sys.a):',
            '...     print(sys.a)', '... ', '5', '5', '5', '5', '5',
            '>>> 1 + 1', '2', '>>> 2 + 2', '4', '>>> '
        ]
        self.assert_paint_ignoring_formatting(screen, (13, 4))
        self.repl.scroll_offset += len(screen) - self.repl.height
        self.assert_paint_ignoring_formatting(screen[9:], (4, 4))
        sys.a = 6
        self.undo()
        screen = [
            INCONSISTENT_HISTORY_MSG[:self.repl.width],
            '6',
            # everything will jump down a line - that's perfectly
            # reasonable
            '>>> 1 + 1',
            '2',
            '>>> ',
            ' '
        ]
        self.assert_paint_ignoring_formatting(screen, (4, 4))
        self.repl.scroll_offset += len(screen) - self.repl.height
        self.assert_paint_ignoring_formatting(screen[1:-1], (3, 4))

    def test_rewind_inconsistent_history_more_lines_lower_screen(self):
        self.repl.width = 60
        sys.a = 5
        self.enter("import sys")
        self.enter("for i in range(sys.a):")
        self.enter("    print(sys.a)")
        self.enter("")
        self.enter("1 + 1")
        self.enter("2 + 2")
        screen = [
            ">>> import sys", ">>> for i in range(sys.a):",
            "...     print(sys.a)", '... ', '5', '5', '5', '5', '5',
            '>>> 1 + 1', '2', '>>> 2 + 2', '4', '>>> '
        ]
        self.assert_paint_ignoring_formatting(screen, (13, 4))
        self.repl.scroll_offset += len(screen) - self.repl.height
        self.assert_paint_ignoring_formatting(screen[9:], (4, 4))
        sys.a = 8
        self.undo()
        screen = [
            INCONSISTENT_HISTORY_MSG[:self.repl.width], '8', '8', '8',
            '>>> 1 + 1', '2', '>>> '
        ]
        self.assert_paint_ignoring_formatting(screen)
        self.repl.scroll_offset += len(screen) - self.repl.height
        self.assert_paint_ignoring_formatting(screen[-5:])

    def test_rewind_inconsistent_history_more_lines_raise_screen(self):
        self.repl.width = 60
        sys.a = 5
        self.enter("import sys")
        self.enter("for i in range(sys.a):")
        self.enter("    print(sys.a)")
        self.enter("")
        self.enter("1 + 1")
        self.enter("2 + 2")
        screen = [
            ">>> import sys", ">>> for i in range(sys.a):",
            "...     print(sys.a)", '... ', '5', '5', '5', '5', '5',
            '>>> 1 + 1', '2', '>>> 2 + 2', '4', '>>> '
        ]
        self.assert_paint_ignoring_formatting(screen, (13, 4))
        self.repl.scroll_offset += len(screen) - self.repl.height
        self.assert_paint_ignoring_formatting(screen[9:], (4, 4))
        sys.a = 1
        self.undo()
        screen = [
            INCONSISTENT_HISTORY_MSG[:self.repl.width], '1', '>>> 1 + 1', '2',
            '>>> ', ' '
        ]
        self.assert_paint_ignoring_formatting(screen)
        self.repl.scroll_offset += len(screen) - self.repl.height
        self.assert_paint_ignoring_formatting(screen[1:-1])

    def test_rewind_history_not_quite_inconsistent(self):
        self.repl.width = 50
        sys.a = 5
        self.enter("for i in range(__import__('sys').a):")
        self.enter("    print(i)")
        self.enter("")
        self.enter("1 + 1")
        self.enter("2 + 2")
        screen = [
            ">>> for i in range(__import__('sys').a):", "...     print(i)",
            "... ", '0', '1', '2', '3', '4', '>>> 1 + 1', '2', '>>> 2 + 2',
            '4', '>>> '
        ]
        self.assert_paint_ignoring_formatting(screen, (12, 4))
        self.repl.scroll_offset += len(screen) - self.repl.height
        self.assert_paint_ignoring_formatting(screen[8:], (4, 4))
        sys.a = 6
        self.undo()
        screen = [
            '5',
            # everything will jump down a line - that's perfectly
            # reasonable
            '>>> 1 + 1',
            '2',
            '>>> '
        ]
        self.assert_paint_ignoring_formatting(screen, (3, 4))

    def test_rewind_barely_consistent(self):
        self.enter("1 + 1")
        self.enter("2 + 2")
        self.enter("3 + 3")
        screen = [">>> 1 + 1", '2', '>>> 2 + 2', '4', '>>> 3 + 3', '6', '>>> ']
        self.assert_paint_ignoring_formatting(screen, (6, 4))
        self.repl.scroll_offset += len(screen) - self.repl.height
        self.assert_paint_ignoring_formatting(screen[2:], (4, 4))
        self.repl.display_lines[2] = self.repl.display_lines[2] * 2
        self.undo()
        screen = ['>>> 2 + 2', '4', '>>> ']
        self.assert_paint_ignoring_formatting(screen, (2, 4))

    def test_clear_screen(self):
        self.enter("1 + 1")
        self.enter("2 + 2")
        screen = [">>> 1 + 1", '2', '>>> 2 + 2', '4', '>>> ']
        self.assert_paint_ignoring_formatting(screen, (4, 4))
        self.repl.request_paint_to_clear_screen = True
        screen = [">>> 1 + 1", '2', '>>> 2 + 2', '4', '>>> ', '', '', '', '']
        self.assert_paint_ignoring_formatting(screen, (4, 4))

    def test_scroll_down_while_banner_visible(self):
        self.repl.status_bar.message('STATUS_BAR')
        self.enter("1 + 1")
        self.enter("2 + 2")
        screen = [
            ">>> 1 + 1", '2', '>>> 2 + 2', '4', '>>> ',
            'STATUS_BAR                      '
        ]
        self.assert_paint_ignoring_formatting(screen, (4, 4))
        self.repl.scroll_offset += len(screen) - self.repl.height
        self.assert_paint_ignoring_formatting(screen[1:], (3, 4))

    def test_clear_screen_while_banner_visible(self):
        self.repl.status_bar.message('STATUS_BAR')
        self.enter("1 + 1")
        self.enter("2 + 2")
        screen = [
            ">>> 1 + 1", '2', '>>> 2 + 2', '4', '>>> ',
            'STATUS_BAR                      '
        ]
        self.assert_paint_ignoring_formatting(screen, (4, 4))
        self.repl.scroll_offset += len(screen) - self.repl.height
        self.assert_paint_ignoring_formatting(screen[1:], (3, 4))

        self.repl.request_paint_to_clear_screen = True
        screen = [
            '2', '>>> 2 + 2', '4', '>>> ', '', '', '',
            'STATUS_BAR                      '
        ]
        self.assert_paint_ignoring_formatting(screen, (3, 4))

    def test_cursor_stays_at_bottom_of_screen(self):
        """infobox showing up during intermediate render was causing this to
        fail, #371"""
        self.repl.width = 50
        self.repl.current_line = "__import__('random').__name__"
        with output_to_repl(self.repl):
            self.repl.on_enter(insert_into_history=False)
        screen = [">>> __import__('random').__name__", "'random'"]
        self.assert_paint_ignoring_formatting(screen)

        with output_to_repl(self.repl):
            self.repl.process_event(self.refresh_requests.pop())
        screen = [">>> __import__('random').__name__", "'random'", ""]
        self.assert_paint_ignoring_formatting(screen)

        with output_to_repl(self.repl):
            self.repl.process_event(self.refresh_requests.pop())
        screen = [">>> __import__('random').__name__", "'random'", ">>> "]
        self.assert_paint_ignoring_formatting(screen, (2, 4))

    def test_unhighlight_paren_bugs(self):
        """two previous bugs, paren did't highlight until next render
        and paren didn't unhighlight until enter"""
        self.assertEqual(self.repl.rl_history.entries, [''])
        self.enter('(')
        self.assertEqual(self.repl.rl_history.entries, [''])
        screen = [">>> (", "... "]
        self.assertEqual(self.repl.rl_history.entries, [''])
        self.assert_paint_ignoring_formatting(screen)
        self.assertEqual(self.repl.rl_history.entries, [''])

        with output_to_repl(self.repl):
            self.assertEqual(self.repl.rl_history.entries, [''])
            self.repl.process_event(')')
            self.assertEqual(self.repl.rl_history.entries, [''])
        screen = fsarray([
            cyan(">>> ") + on_magenta(bold(red('('))),
            green("... ") + on_magenta(bold(red(')')))
        ])
        self.assert_paint(screen, (1, 5))

        with output_to_repl(self.repl):
            self.repl.process_event(' ')
        screen = fsarray([
            cyan(">>> ") + yellow('('),
            green("... ") + yellow(')') + bold(cyan(" "))
        ])
        self.assert_paint(screen, (1, 6))

    def send_key(self, key):
        self.repl.process_event('<SPACE>' if key == ' ' else key)
        self.repl.paint()  # has some side effects we need to be wary of

    def test_472(self):
        [self.send_key(c) for c in "(1, 2, 3)"]
        with output_to_repl(self.repl):
            self.send_key('\n')
            self.send_refreshes()
            self.send_key('<UP>')
            self.repl.paint()
            [self.send_key('<LEFT>') for _ in range(4)]
            self.send_key('<BACKSPACE>')
            self.send_key('4')
            self.repl.on_enter()
            self.send_refreshes()
        screen = [
            ">>> (1, 2, 3)", '(1, 2, 3)', '>>> (1, 4, 3)', '(1, 4, 3)', '>>> '
        ]
        self.assert_paint_ignoring_formatting(screen, (4, 4))
class TestCurtsiesRewindRedraw(CurtsiesPaintingTest):
    def refresh(self):
        self.refresh_requests.append(RefreshRequestEvent())

    def send_refreshes(self):
        while self.refresh_requests:
            self.repl.process_event(self.refresh_requests.pop())
            _, _ = self.repl.paint()

    def enter(self, line=None):
        """Enter a line of text, avoiding autocompletion windows

        autocomplete could still happen if the entered line has
        autocompletion that would happen then, but intermediate
        stages won't happen"""
        if line is not None:
            self.repl.current_line = line
        with output_to_repl(self.repl):
            self.repl.on_enter(insert_into_history=False)
            self.assertEqual(self.repl.rl_history.entries, [''])
            self.send_refreshes()

    def undo(self):
        with output_to_repl(self.repl):
            self.repl.undo()
            self.send_refreshes()

    def setUp(self):
        self.refresh_requests = []
        self.repl = Repl(banner='', config=setup_config(),
                         request_refresh=self.refresh)
        # clear history
        self.repl.rl_history = History()
        self.repl.height, self.repl.width = (5, 32)

    def test_rewind(self):
        self.repl.current_line = '1 + 1'
        self.enter()
        screen = [u'>>> 1 + 1',
                  u'2',
                  u'>>> ']
        self.assert_paint_ignoring_formatting(screen, (2, 4))
        self.repl.undo()
        screen = [u'>>> ']
        self.assert_paint_ignoring_formatting(screen, (0, 4))

    def test_rewind_contiguity_loss(self):
        self.enter('1 + 1')
        self.enter('2 + 2')
        self.enter('def foo(x):')
        self.repl.current_line = '    return x + 1'
        screen = [u'>>> 1 + 1',
                  u'2',
                  u'>>> 2 + 2',
                  u'4',
                  u'>>> def foo(x):',
                  u'...     return x + 1']
        self.assert_paint_ignoring_formatting(screen, (5, 8))
        self.repl.scroll_offset = 1
        self.assert_paint_ignoring_formatting(screen[1:], (4, 8))
        self.undo()
        screen = [u'2',
                  u'>>> 2 + 2',
                  u'4',
                  u'>>> ']
        self.assert_paint_ignoring_formatting(screen, (3, 4))
        self.undo()
        screen = [u'2',
                  u'>>> ']
        self.assert_paint_ignoring_formatting(screen, (1, 4))
        self.undo()
        screen = [CONTIGUITY_BROKEN_MSG[:self.repl.width],
                  u'>>> ',
                  u'',
                  u'',
                  u'',
                  u' ']  # TODO why is that there? Necessary?
        self.assert_paint_ignoring_formatting(screen, (1, 4))
        screen = [u'>>> ']
        self.assert_paint_ignoring_formatting(screen, (0, 4))

    def test_inconsistent_history_doesnt_happen_if_onscreen(self):
        self.enter("1 + 1")
        screen = [u">>> 1 + 1",
                  u'2',
                  u'>>> ']
        self.assert_paint_ignoring_formatting(screen, (2, 4))
        self.enter("2 + 2")
        screen = [u">>> 1 + 1",
                  u'2',
                  u'>>> 2 + 2',
                  u'4',
                  u'>>> ']
        self.assert_paint_ignoring_formatting(screen, (4, 4))
        self.repl.display_lines[0] = self.repl.display_lines[0] * 2
        self.undo()
        screen = [u">>> 1 + 1",
                  u'2',
                  u'>>> ']
        self.assert_paint_ignoring_formatting(screen, (2, 4))

    def test_rewind_inconsistent_history(self):
        self.enter("1 + 1")
        self.enter("2 + 2")
        self.enter("3 + 3")
        screen = [u">>> 1 + 1",
                  u'2',
                  u'>>> 2 + 2',
                  u'4',
                  u'>>> 3 + 3',
                  u'6',
                  u'>>> ']
        self.assert_paint_ignoring_formatting(screen, (6, 4))
        self.repl.scroll_offset += len(screen) - self.repl.height
        self.assert_paint_ignoring_formatting(screen[2:], (4, 4))
        self.repl.display_lines[0] = self.repl.display_lines[0] * 2
        self.undo()
        screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width],
                  u'>>> 2 + 2',
                  u'4',
                  u'>>> ',
                  u'',
                  u' ']
        self.assert_paint_ignoring_formatting(screen, (3, 4))
        self.repl.scroll_offset += len(screen) - self.repl.height
        self.assert_paint_ignoring_formatting(screen[1:-2], (2, 4))
        self.assert_paint_ignoring_formatting(screen[1:-2], (2, 4))

    def test_rewind_inconsistent_history_more_lines_same_screen(self):
        self.repl.width = 60
        sys.a = 5
        self.enter("import sys")
        self.enter("for i in range(sys.a): print(sys.a)")
        self.enter()
        self.enter("1 + 1")
        self.enter("2 + 2")
        screen = [u">>> import sys",
                  u">>> for i in range(sys.a): print(sys.a)",
                  u'... ',
                  u'5',
                  u'5',
                  u'5',
                  u'5',
                  u'5',
                  u'>>> 1 + 1',
                  u'2',
                  u'>>> 2 + 2',
                  u'4',
                  u'>>> ']
        self.assert_paint_ignoring_formatting(screen, (12, 4))
        self.repl.scroll_offset += len(screen) - self.repl.height
        self.assert_paint_ignoring_formatting(screen[8:], (4, 4))
        sys.a = 6
        self.undo()
        screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width],
                  u'6',
                  # everything will jump down a line - that's perfectly
                  # reasonable
                  u'>>> 1 + 1',
                  u'2',
                  u'>>> ',
                  u' ']
        self.assert_paint_ignoring_formatting(screen, (4, 4))
        self.repl.scroll_offset += len(screen) - self.repl.height
        self.assert_paint_ignoring_formatting(screen[1:-1], (3, 4))

    def test_rewind_inconsistent_history_more_lines_lower_screen(self):
        self.repl.width = 60
        sys.a = 5
        self.enter("import sys")
        self.enter("for i in range(sys.a): print(sys.a)")
        self.enter()
        self.enter("1 + 1")
        self.enter("2 + 2")
        screen = [u">>> import sys",
                  u">>> for i in range(sys.a): print(sys.a)",
                  u'... ',
                  u'5',
                  u'5',
                  u'5',
                  u'5',
                  u'5',
                  u'>>> 1 + 1',
                  u'2',
                  u'>>> 2 + 2',
                  u'4',
                  u'>>> ']
        self.assert_paint_ignoring_formatting(screen, (12, 4))
        self.repl.scroll_offset += len(screen) - self.repl.height
        self.assert_paint_ignoring_formatting(screen[8:], (4, 4))
        sys.a = 8
        self.undo()
        screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width],
                  u'8',
                  u'8',
                  u'8',
                  u'>>> 1 + 1',
                  u'2',
                  u'>>> ']
        self.assert_paint_ignoring_formatting(screen)
        self.repl.scroll_offset += len(screen) - self.repl.height
        self.assert_paint_ignoring_formatting(screen[-5:])

    def test_rewind_inconsistent_history_more_lines_raise_screen(self):
        self.repl.width = 60
        sys.a = 5
        self.enter("import sys")
        self.enter("for i in range(sys.a): print(sys.a)")
        self.enter()
        self.enter("1 + 1")
        self.enter("2 + 2")
        screen = [u">>> import sys",
                  u">>> for i in range(sys.a): print(sys.a)",
                  u'... ',
                  u'5',
                  u'5',
                  u'5',
                  u'5',
                  u'5',
                  u'>>> 1 + 1',
                  u'2',
                  u'>>> 2 + 2',
                  u'4',
                  u'>>> ']
        self.assert_paint_ignoring_formatting(screen, (12, 4))
        self.repl.scroll_offset += len(screen) - self.repl.height
        self.assert_paint_ignoring_formatting(screen[8:], (4, 4))
        sys.a = 1
        self.undo()
        screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width],
                  u'1',
                  u'>>> 1 + 1',
                  u'2',
                  u'>>> ',
                  u' ']
        self.assert_paint_ignoring_formatting(screen)
        self.repl.scroll_offset += len(screen) - self.repl.height
        self.assert_paint_ignoring_formatting(screen[1:-1])

    def test_rewind_history_not_quite_inconsistent(self):
        self.repl.width = 50
        sys.a = 5
        self.enter("for i in range(__import__('sys').a): print(i)")
        self.enter()
        self.enter("1 + 1")
        self.enter("2 + 2")
        screen = [u">>> for i in range(__import__('sys').a): print(i)",
                  u'... ',
                  u'0',
                  u'1',
                  u'2',
                  u'3',
                  u'4',
                  u'>>> 1 + 1',
                  u'2',
                  u'>>> 2 + 2',
                  u'4',
                  u'>>> ']
        self.assert_paint_ignoring_formatting(screen, (11, 4))
        self.repl.scroll_offset += len(screen) - self.repl.height
        self.assert_paint_ignoring_formatting(screen[7:], (4, 4))
        sys.a = 6
        self.undo()
        screen = [u'5',
                  # everything will jump down a line - that's perfectly
                  # reasonable
                  u'>>> 1 + 1',
                  u'2',
                  u'>>> ']
        self.assert_paint_ignoring_formatting(screen, (3, 4))

    def test_rewind_barely_consistent(self):
        self.enter("1 + 1")
        self.enter("2 + 2")
        self.enter("3 + 3")
        screen = [u">>> 1 + 1",
                  u'2',
                  u'>>> 2 + 2',
                  u'4',
                  u'>>> 3 + 3',
                  u'6',
                  u'>>> ']
        self.assert_paint_ignoring_formatting(screen, (6, 4))
        self.repl.scroll_offset += len(screen) - self.repl.height
        self.assert_paint_ignoring_formatting(screen[2:], (4, 4))
        self.repl.display_lines[2] = self.repl.display_lines[2] * 2
        self.undo()
        screen = [u'>>> 2 + 2',
                  u'4',
                  u'>>> ']
        self.assert_paint_ignoring_formatting(screen, (2, 4))

    def test_clear_screen(self):
        self.enter("1 + 1")
        self.enter("2 + 2")
        screen = [u">>> 1 + 1",
                  u'2',
                  u'>>> 2 + 2',
                  u'4',
                  u'>>> ']
        self.assert_paint_ignoring_formatting(screen, (4, 4))
        self.repl.request_paint_to_clear_screen = True
        screen = [u">>> 1 + 1",
                  u'2',
                  u'>>> 2 + 2',
                  u'4',
                  u'>>> ', u'', u'', u'', u'']
        self.assert_paint_ignoring_formatting(screen, (4, 4))

    def test_scroll_down_while_banner_visible(self):
        self.repl.status_bar.message('STATUS_BAR')
        self.enter("1 + 1")
        self.enter("2 + 2")
        screen = [u">>> 1 + 1",
                  u'2',
                  u'>>> 2 + 2',
                  u'4',
                  u'>>> ',
                  u'STATUS_BAR                      ']
        self.assert_paint_ignoring_formatting(screen, (4, 4))
        self.repl.scroll_offset += len(screen) - self.repl.height
        self.assert_paint_ignoring_formatting(screen[1:], (3, 4))

    def test_clear_screen_while_banner_visible(self):
        self.repl.status_bar.message('STATUS_BAR')
        self.enter("1 + 1")
        self.enter("2 + 2")
        screen = [u">>> 1 + 1",
                  u'2',
                  u'>>> 2 + 2',
                  u'4',
                  u'>>> ',
                  u'STATUS_BAR                      ']
        self.assert_paint_ignoring_formatting(screen, (4, 4))
        self.repl.scroll_offset += len(screen) - self.repl.height
        self.assert_paint_ignoring_formatting(screen[1:], (3, 4))

        self.repl.request_paint_to_clear_screen = True
        screen = [u'2',
                  u'>>> 2 + 2',
                  u'4',
                  u'>>> ',
                  u'', u'', u'',
                  u'STATUS_BAR                      ']
        self.assert_paint_ignoring_formatting(screen, (3, 4))

    def test_cursor_stays_at_bottom_of_screen(self):
        """infobox showing up during intermediate render was causing this to
        fail, #371"""
        self.repl.width = 50
        self.repl.current_line = "__import__('random').__name__"
        with output_to_repl(self.repl):
            self.repl.on_enter(insert_into_history=False)
        screen = [u">>> __import__('random').__name__",
                  u"'random'"]
        self.assert_paint_ignoring_formatting(screen)

        with output_to_repl(self.repl):
            self.repl.process_event(self.refresh_requests.pop())
        screen = [u">>> __import__('random').__name__",
                  u"'random'",
                  u""]
        self.assert_paint_ignoring_formatting(screen)

        with output_to_repl(self.repl):
            self.repl.process_event(self.refresh_requests.pop())
        screen = [u">>> __import__('random').__name__",
                  u"'random'",
                  u">>> "]
        self.assert_paint_ignoring_formatting(screen, (2, 4))

    def test_unhighlight_paren_bugs(self):
        """two previous bugs, paren did't highlight until next render
        and paren didn't unhighlight until enter"""
        self.assertEqual(self.repl.rl_history.entries, [''])
        self.enter('(')
        self.assertEqual(self.repl.rl_history.entries, [''])
        screen = [u">>> (",
                  u"... "]
        self.assertEqual(self.repl.rl_history.entries, [''])
        self.assert_paint_ignoring_formatting(screen)
        self.assertEqual(self.repl.rl_history.entries, [''])

        with output_to_repl(self.repl):
            self.assertEqual(self.repl.rl_history.entries, [''])
            self.repl.process_event(')')
            self.assertEqual(self.repl.rl_history.entries, [''])
        screen = fsarray([cyan(u">>> ")+on_magenta(bold(red('('))),
                         green(u"... ")+on_magenta(bold(red(')')))])
        self.assert_paint(screen, (1, 5))

        with output_to_repl(self.repl):
            self.repl.process_event(' ')
        screen = fsarray([cyan(u">>> ")+yellow('('),
                         green(u"... ")+yellow(')')+bold(cyan(" "))])
        self.assert_paint(screen, (1, 6))

    def send_key(self, key):
        self.repl.process_event(u'<SPACE>' if key == ' ' else key)
        self.repl.paint()  # has some side effects we need to be wary of

    def test_472(self):
        [self.send_key(c) for c in "(1, 2, 3)"]
        with output_to_repl(self.repl):
            self.send_key('\n')
            self.send_refreshes()
            self.send_key('<UP>')
            self.repl.paint()
            [self.send_key('<LEFT>') for _ in range(4)]
            self.send_key('<BACKSPACE>')
            self.send_key('4')
            self.repl.on_enter()
            self.send_refreshes()
        screen = [">>> (1, 2, 3)",
                  '(1, 2, 3)',
                  '>>> (1, 4, 3)',
                  '(1, 4, 3)',
                  '>>> ']
        self.assert_paint_ignoring_formatting(screen, (4, 4))