From 5e795132aa8657b6dcbcc4bebec2c45312c0f1bc Mon Sep 17 00:00:00 2001 From: Jayson Messenger Date: Wed, 25 Jul 2018 10:32:18 -0400 Subject: [PATCH 1/7] Add some unit tests for moust scrolling Note: Scroll beyond end does not work for mouse scrolling --- tests/test_mouse_scroll.py | 126 +++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 tests/test_mouse_scroll.py diff --git a/tests/test_mouse_scroll.py b/tests/test_mouse_scroll.py new file mode 100644 index 0000000000..a84bc7049b --- /dev/null +++ b/tests/test_mouse_scroll.py @@ -0,0 +1,126 @@ +from __future__ import unicode_literals + +from prompt_toolkit.layout.screen import Screen, WritePosition +from prompt_toolkit.layout.mouse_handlers import MouseHandlers +from prompt_toolkit.layout.containers import Window +from prompt_toolkit.layout.controls import BufferControl +from prompt_toolkit.buffer import Buffer + +import pytest + +width = 10 +height = 4 + +@pytest.fixture +def dim(): + return { "base_width":10, + "win_width":10, + "win_height":4, + "buf_height": 40 } + + +@pytest.fixture +def content(dim): + buff = Buffer() + line = "*" * dim["base_width"] + for i in range(dim["buf_height"]): + buff.insert_text(line) + buff.newline() + buff.cursor_up(dim["buf_height"] + 1) + control = BufferControl(buffer=buff) + return control + + +@pytest.fixture +def window(content, dim): + win = Window(content=content) + win.reset() + update(win, dim) + assert win.vertical_scroll == 0 + assert win.render_info.ui_content.cursor_position.y == 0 + return win + +@pytest.fixture +def window_allow_scroll(content, dim): + win = Window(content=content, allow_scroll_beyond_bottom=True) + win.reset() + update(win, dim) + assert win.vertical_scroll == 0 + assert win.render_info.ui_content.cursor_position.y == 0 + return win + +def scroll(window, dim, direction, count, delay_update=False): + for i in range(count): + if direction == "up": + window._scroll_up() + elif direction == "down": + window._scroll_down() + else: + # Just make sure we don't do this on accident + assert False + if not delay_update: + update(window, dim) + + if delay_update: + update(window, dim) + + +def update(window, dim): + window.write_to_screen(Screen(), + MouseHandlers(), + WritePosition(xpos=0, + ypos=0, + width=dim["win_width"], + height=dim["win_height"]), + parent_style='', + erase_bg=False, + z_index=None) + + +def check_scroll(window, vertical_scroll, cursor_y): + assert window.vertical_scroll == vertical_scroll + assert window.render_info.ui_content.cursor_position.y == cursor_y + + +def test_scroll_down(window, dim): + scroll(window, dim, "down", 1) + check_scroll(window, 1, 1) + + scroll(window, dim, "down", 2) + check_scroll(window, 3, 3) + + scroll(window, dim, "down", 4) + check_scroll(window, 7, 7) + +def test_scroll_up(window, dim): + test_scroll_down(window, dim) + scroll(window, dim, "up", 1) + check_scroll(window, 6, 7) + scroll(window, dim, "up", 2) + check_scroll(window, 4, 7) + scroll(window, dim, "up", 1) + check_scroll(window, 3, 6) + +def test_multiple_scroll_before_render(window, dim): + scroll(window, dim, "down", 10, delay_update=True) + check_scroll(window, 10, 10) + + scroll(window, dim, "up", 1, delay_update=True) + scroll(window, dim, "down", 1, delay_update=True) + check_scroll(window, 10, 10) + +def test_scroll_to_end(window, dim): + # Regardless of how much we scroll, we should stop with the + # last line of the window the last line of the buffer + last_line = dim["buf_height"] - dim["win_height"] + 1 + + scroll(window, dim, "down", dim["buf_height"]*2) + check_scroll(window, last_line, last_line) + +def test_scroll_beyond_end(window_allow_scroll, dim): + + last_line = dim["buf_height"] - dim["win_height"] + 1 + scroll(window_allow_scroll, dim, "down", last_line) + check_scroll(window_allow_scroll, last_line, last_line) + scroll(window_allow_scroll, dim, "down", 1) + check_scroll(window_allow_scroll, last_line + 1, last_line + 1) From 9c60a7b8890c446b3750a8cf2dde67a37ec78c5f Mon Sep 17 00:00:00 2001 From: Jayson Messenger Date: Wed, 25 Jul 2018 10:36:18 -0400 Subject: [PATCH 2/7] Allow mouse scrolling beyond end --- prompt_toolkit/layout/containers.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/prompt_toolkit/layout/containers.py b/prompt_toolkit/layout/containers.py index bea9ac039c..76c5995db9 100644 --- a/prompt_toolkit/layout/containers.py +++ b/prompt_toolkit/layout/containers.py @@ -2014,10 +2014,17 @@ def _scroll_down(self): info = self.render_info if self.vertical_scroll < info.content_height - info.window_height: + should_scroll = True + elif self.allow_scroll_beyond_bottom() and self.vertical_scroll < info.content_height: + should_scroll = True + else: + should_scroll = False + + if should_scroll: + self.vertical_scroll += 1 if info.cursor_position.y <= info.configured_scroll_offsets.top: self.content.move_cursor_down() - self.vertical_scroll += 1 def _scroll_up(self): " Scroll window up. " From af43a4c9dcc3e3a888d304c1785ed9b9859791ca Mon Sep 17 00:00:00 2001 From: Jayson Messenger Date: Wed, 25 Jul 2018 11:10:13 -0400 Subject: [PATCH 3/7] Removed extra newline in scroll tests --- tests/test_mouse_scroll.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/test_mouse_scroll.py b/tests/test_mouse_scroll.py index a84bc7049b..846df52249 100644 --- a/tests/test_mouse_scroll.py +++ b/tests/test_mouse_scroll.py @@ -23,9 +23,11 @@ def dim(): def content(dim): buff = Buffer() line = "*" * dim["base_width"] - for i in range(dim["buf_height"]): + for i in range(dim["buf_height"] - 1): buff.insert_text(line) buff.newline() + buff.insert_text(line) + buff.cursor_up(dim["buf_height"] + 1) control = BufferControl(buffer=buff) return control @@ -112,15 +114,25 @@ def test_multiple_scroll_before_render(window, dim): def test_scroll_to_end(window, dim): # Regardless of how much we scroll, we should stop with the # last line of the window the last line of the buffer - last_line = dim["buf_height"] - dim["win_height"] + 1 + last_line = dim["buf_height"] - dim["win_height"] scroll(window, dim, "down", dim["buf_height"]*2) check_scroll(window, last_line, last_line) -def test_scroll_beyond_end(window_allow_scroll, dim): +def test_scroll_past_end_before_render(window, dim): + # Regardless of how much we scroll, we should stop with the + # last line of the window the last line of the buffer + last_line = dim["buf_height"] - dim["win_height"] - last_line = dim["buf_height"] - dim["win_height"] + 1 + scroll(window, dim, "down", dim["buf_height"]*2, delay_update=True) + check_scroll(window, last_line, last_line) + +def test_scroll_beyond_end(window_allow_scroll, dim): + last_line = dim["buf_height"] - dim["win_height"] scroll(window_allow_scroll, dim, "down", last_line) check_scroll(window_allow_scroll, last_line, last_line) scroll(window_allow_scroll, dim, "down", 1) check_scroll(window_allow_scroll, last_line + 1, last_line + 1) + # Max out scrolling + scroll(window_allow_scroll, dim, "down", dim["buf_height"]) + check_scroll(window_allow_scroll, dim["buf_height"] - 1, dim["buf_height"] - 1) From a7b75b9294ae5afdc81d19d0bbf52d84f78cb553 Mon Sep 17 00:00:00 2001 From: Jayson Messenger Date: Wed, 25 Jul 2018 11:24:09 -0400 Subject: [PATCH 4/7] Add in empty window check --- tests/test_mouse_scroll.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/test_mouse_scroll.py b/tests/test_mouse_scroll.py index 846df52249..d20ac88da8 100644 --- a/tests/test_mouse_scroll.py +++ b/tests/test_mouse_scroll.py @@ -33,6 +33,15 @@ def content(dim): return control +@pytest.fixture +def empty_window(dim): + win = Window(content=BufferControl(buffer=Buffer())) + win.reset() + update(win, dim) + assert win.vertical_scroll == 0 + assert win.render_info.ui_content.cursor_position.y == 0 + return win + @pytest.fixture def window(content, dim): win = Window(content=content) @@ -84,6 +93,12 @@ def check_scroll(window, vertical_scroll, cursor_y): assert window.render_info.ui_content.cursor_position.y == cursor_y +def test_empty_window(empty_window, dim): + scroll(empty_window, dim, "down", 1) + check_scroll(empty_window, 0, 0) + scroll(empty_window, dim, "up", 1) + check_scroll(empty_window, 0, 0) + def test_scroll_down(window, dim): scroll(window, dim, "down", 1) check_scroll(window, 1, 1) @@ -102,6 +117,8 @@ def test_scroll_up(window, dim): check_scroll(window, 4, 7) scroll(window, dim, "up", 1) check_scroll(window, 3, 6) + scroll(window, dim, "up", dim["buf_height"] * 2) + check_scroll(window, 0, dim["win_height"] - 1) def test_multiple_scroll_before_render(window, dim): scroll(window, dim, "down", 10, delay_update=True) @@ -116,7 +133,7 @@ def test_scroll_to_end(window, dim): # last line of the window the last line of the buffer last_line = dim["buf_height"] - dim["win_height"] - scroll(window, dim, "down", dim["buf_height"]*2) + scroll(window, dim, "down", dim["buf_height"] * 2) check_scroll(window, last_line, last_line) def test_scroll_past_end_before_render(window, dim): @@ -136,3 +153,4 @@ def test_scroll_beyond_end(window_allow_scroll, dim): # Max out scrolling scroll(window_allow_scroll, dim, "down", dim["buf_height"]) check_scroll(window_allow_scroll, dim["buf_height"] - 1, dim["buf_height"] - 1) + From 25e8c0f0030991e1218013dee76056c834f450ee Mon Sep 17 00:00:00 2001 From: Jayson Messenger Date: Wed, 25 Jul 2018 15:17:49 -0400 Subject: [PATCH 5/7] Added line wrapping tests for scrolling down Note: Scrolling to end is broken --- tests/test_mouse_scroll.py | 174 +++++++++++++++++++++++++------------ 1 file changed, 119 insertions(+), 55 deletions(-) diff --git a/tests/test_mouse_scroll.py b/tests/test_mouse_scroll.py index d20ac88da8..a2ec22c8c5 100644 --- a/tests/test_mouse_scroll.py +++ b/tests/test_mouse_scroll.py @@ -8,58 +8,6 @@ import pytest -width = 10 -height = 4 - -@pytest.fixture -def dim(): - return { "base_width":10, - "win_width":10, - "win_height":4, - "buf_height": 40 } - - -@pytest.fixture -def content(dim): - buff = Buffer() - line = "*" * dim["base_width"] - for i in range(dim["buf_height"] - 1): - buff.insert_text(line) - buff.newline() - buff.insert_text(line) - - buff.cursor_up(dim["buf_height"] + 1) - control = BufferControl(buffer=buff) - return control - - -@pytest.fixture -def empty_window(dim): - win = Window(content=BufferControl(buffer=Buffer())) - win.reset() - update(win, dim) - assert win.vertical_scroll == 0 - assert win.render_info.ui_content.cursor_position.y == 0 - return win - -@pytest.fixture -def window(content, dim): - win = Window(content=content) - win.reset() - update(win, dim) - assert win.vertical_scroll == 0 - assert win.render_info.ui_content.cursor_position.y == 0 - return win - -@pytest.fixture -def window_allow_scroll(content, dim): - win = Window(content=content, allow_scroll_beyond_bottom=True) - win.reset() - update(win, dim) - assert win.vertical_scroll == 0 - assert win.render_info.ui_content.cursor_position.y == 0 - return win - def scroll(window, dim, direction, count, delay_update=False): for i in range(count): if direction == "up": @@ -88,9 +36,27 @@ def update(window, dim): z_index=None) -def check_scroll(window, vertical_scroll, cursor_y): - assert window.vertical_scroll == vertical_scroll - assert window.render_info.ui_content.cursor_position.y == cursor_y +def check_scroll(window, expected_vertical_scroll, expected_cursor_y): + assert window.vertical_scroll == expected_vertical_scroll + assert window.render_info.ui_content.cursor_position.y == expected_cursor_y + + +@pytest.fixture +def dim(): + return { "base_width":10, + "win_width":10, + "win_height":4, + "buf_height": 40 } + + +@pytest.fixture +def empty_window(dim): + win = Window(content=BufferControl(buffer=Buffer())) + win.reset() + update(win, dim) + assert win.vertical_scroll == 0 + assert win.render_info.ui_content.cursor_position.y == 0 + return win def test_empty_window(empty_window, dim): @@ -99,6 +65,31 @@ def test_empty_window(empty_window, dim): scroll(empty_window, dim, "up", 1) check_scroll(empty_window, 0, 0) + +@pytest.fixture +def content(dim): + buff = Buffer() + line = "*" * dim["base_width"] + for i in range(dim["buf_height"] - 1): + buff.insert_text(line) + buff.newline() + buff.insert_text(line) + + buff.cursor_position = 0 + control = BufferControl(buffer=buff) + return control + + +@pytest.fixture +def window(content, dim): + win = Window(content=content) + win.reset() + update(win, dim) + assert win.vertical_scroll == 0 + assert win.render_info.ui_content.cursor_position.y == 0 + return win + + def test_scroll_down(window, dim): scroll(window, dim, "down", 1) check_scroll(window, 1, 1) @@ -144,6 +135,16 @@ def test_scroll_past_end_before_render(window, dim): scroll(window, dim, "down", dim["buf_height"]*2, delay_update=True) check_scroll(window, last_line, last_line) +@pytest.fixture +def window_allow_scroll(content, dim): + win = Window(content=content, allow_scroll_beyond_bottom=True) + win.reset() + update(win, dim) + assert win.vertical_scroll == 0 + assert win.render_info.ui_content.cursor_position.y == 0 + return win + + def test_scroll_beyond_end(window_allow_scroll, dim): last_line = dim["buf_height"] - dim["win_height"] scroll(window_allow_scroll, dim, "down", last_line) @@ -154,3 +155,66 @@ def test_scroll_beyond_end(window_allow_scroll, dim): scroll(window_allow_scroll, dim, "down", dim["buf_height"]) check_scroll(window_allow_scroll, dim["buf_height"] - 1, dim["buf_height"] - 1) + +@pytest.fixture +def content_line_wrap(dim): + buff = Buffer() + line = "*" * dim["base_width"] + for i in range(dim["buf_height"]//2): + buff.insert_text(line) + buff.newline() + + for i in range(dim["buf_height"]//2 - 1): + buff.insert_text(line * 2) + buff.newline() + buff.insert_text(line * 2) + + buff.cursor_position = 0 + control = BufferControl(buffer=buff) + return control + + +@pytest.fixture +def window_line_wrap(content_line_wrap, dim): + win = Window(content=content_line_wrap, wrap_lines=True) + win.reset() + update(win, dim) + assert win.vertical_scroll == 0 + assert win.render_info.ui_content.cursor_position.y == 0 + return win + + +def test_scroll_down_with_line_wrap(window_line_wrap, dim): + scroll(window_line_wrap, dim, "down", 1) + check_scroll(window_line_wrap, 1, 1) + + scroll(window_line_wrap, dim, "down", 2) + check_scroll(window_line_wrap, 3, 3) + + scroll(window_line_wrap, dim, "down", 2) + check_scroll(window_line_wrap, 5, 5) + + mid = dim["buf_height"]//2 + # Scroll to just before change in line length + scroll(window_line_wrap, dim, "down", mid - 5 - dim["win_height"]) + check_scroll(window_line_wrap, + mid - dim["win_height"], + mid - dim["win_height"]) + + scroll(window_line_wrap, dim, "down", 1) + check_scroll(window_line_wrap, + mid - dim["win_height"] + 1, + mid - dim["win_height"] + 1) + + scroll(window_line_wrap, dim, "down", dim["win_height"] - 1) + check_scroll(window_line_wrap, mid, mid) + + scroll(window_line_wrap, dim, "down", 1) + check_scroll(window_line_wrap, mid + 1, mid + 1) + +def test_scroll_to_end_with_line_wrap(window_line_wrap, dim): + # Later lines take two lines in the window + last_line = dim["buf_height"] - dim["win_height"]//2 + + scroll(window_line_wrap, dim, "down", dim["buf_height"] * 2) + check_scroll(window_line_wrap, last_line, last_line) From e1bdd655848bb2fcc61302baa9de42335bc6c641 Mon Sep 17 00:00:00 2001 From: Jayson Messenger Date: Wed, 25 Jul 2018 16:55:39 -0400 Subject: [PATCH 6/7] Fix scroll down with linewrapping on --- prompt_toolkit/layout/containers.py | 14 +++++++-- tests/test_mouse_scroll.py | 45 ++++++++++++++++++++--------- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/prompt_toolkit/layout/containers.py b/prompt_toolkit/layout/containers.py index 76c5995db9..776ec6bbfe 100644 --- a/prompt_toolkit/layout/containers.py +++ b/prompt_toolkit/layout/containers.py @@ -2015,10 +2015,20 @@ def _scroll_down(self): if self.vertical_scroll < info.content_height - info.window_height: should_scroll = True - elif self.allow_scroll_beyond_bottom() and self.vertical_scroll < info.content_height: + elif self.allow_scroll_beyond_bottom() and self.vertical_scroll < info.content_height - 1: should_scroll = True - else: + elif self.vertical_scroll == info.content_height - 1: should_scroll = False + else: + # Cycle through remaining lines for a line with more than one line + height = 0 + for line in range(self.vertical_scroll, info.content_height): + height += info.get_height_for_line(line) + if height > info.window_height: + should_scroll = True + break + else: + should_scroll = False if should_scroll: self.vertical_scroll += 1 diff --git a/tests/test_mouse_scroll.py b/tests/test_mouse_scroll.py index a2ec22c8c5..347d739ddb 100644 --- a/tests/test_mouse_scroll.py +++ b/tests/test_mouse_scroll.py @@ -8,7 +8,7 @@ import pytest -def scroll(window, dim, direction, count, delay_update=False): +def scroll(window, dim, direction, count, delay_update=True): for i in range(count): if direction == "up": window._scroll_up() @@ -43,7 +43,7 @@ def check_scroll(window, expected_vertical_scroll, expected_cursor_y): @pytest.fixture def dim(): - return { "base_width":10, + return { "base_width":9, "win_width":10, "win_height":4, "buf_height": 40 } @@ -69,7 +69,7 @@ def test_empty_window(empty_window, dim): @pytest.fixture def content(dim): buff = Buffer() - line = "*" * dim["base_width"] + line = "*" * (dim["base_width"]) for i in range(dim["buf_height"] - 1): buff.insert_text(line) buff.newline() @@ -91,32 +91,32 @@ def window(content, dim): def test_scroll_down(window, dim): - scroll(window, dim, "down", 1) + scroll(window, dim, "down", 1, delay_update=False) check_scroll(window, 1, 1) - scroll(window, dim, "down", 2) + scroll(window, dim, "down", 2, delay_update=False) check_scroll(window, 3, 3) - scroll(window, dim, "down", 4) + scroll(window, dim, "down", 4, delay_update=False) check_scroll(window, 7, 7) def test_scroll_up(window, dim): test_scroll_down(window, dim) - scroll(window, dim, "up", 1) + scroll(window, dim, "up", 1, delay_update=False) check_scroll(window, 6, 7) - scroll(window, dim, "up", 2) + scroll(window, dim, "up", 2, delay_update=False) check_scroll(window, 4, 7) - scroll(window, dim, "up", 1) + scroll(window, dim, "up", 1, delay_update=False) check_scroll(window, 3, 6) - scroll(window, dim, "up", dim["buf_height"] * 2) + scroll(window, dim, "up", 6, delay_update=False) check_scroll(window, 0, dim["win_height"] - 1) def test_multiple_scroll_before_render(window, dim): - scroll(window, dim, "down", 10, delay_update=True) + scroll(window, dim, "down", 10) check_scroll(window, 10, 10) - scroll(window, dim, "up", 1, delay_update=True) - scroll(window, dim, "down", 1, delay_update=True) + scroll(window, dim, "up", 1) + scroll(window, dim, "down", 1) check_scroll(window, 10, 10) def test_scroll_to_end(window, dim): @@ -218,3 +218,22 @@ def test_scroll_to_end_with_line_wrap(window_line_wrap, dim): scroll(window_line_wrap, dim, "down", dim["buf_height"] * 2) check_scroll(window_line_wrap, last_line, last_line) + +@pytest.fixture +def window_allow_scroll_and_line_wrap(content_line_wrap, dim): + win = Window(content=content_line_wrap, allow_scroll_beyond_bottom=True) + win.reset() + update(win, dim) + assert win.vertical_scroll == 0 + assert win.render_info.ui_content.cursor_position.y == 0 + return win + +def test_scroll_beyond_end_with_line_wrap(window_allow_scroll_and_line_wrap, dim): + last_line = dim["buf_height"] - dim["win_height"]//2 + scroll(window_allow_scroll_and_line_wrap, dim, "down", last_line) + check_scroll(window_allow_scroll_and_line_wrap, last_line, last_line) + scroll(window_allow_scroll_and_line_wrap, dim, "down", 1) + check_scroll(window_allow_scroll_and_line_wrap, last_line + 1, last_line + 1) + # Max out scrolling + scroll(window_allow_scroll_and_line_wrap, dim, "down", dim["buf_height"]) + check_scroll(window_allow_scroll_and_line_wrap, dim["buf_height"] - 1, dim["buf_height"] - 1) From 3594662612cdd86c34d0acc05b85aee8a7c36b35 Mon Sep 17 00:00:00 2001 From: Jayson Messenger Date: Wed, 25 Jul 2018 17:45:03 -0400 Subject: [PATCH 7/7] Fix flake8 error --- prompt_toolkit/layout/containers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/prompt_toolkit/layout/containers.py b/prompt_toolkit/layout/containers.py index 776ec6bbfe..029776b52d 100644 --- a/prompt_toolkit/layout/containers.py +++ b/prompt_toolkit/layout/containers.py @@ -2035,7 +2035,6 @@ def _scroll_down(self): if info.cursor_position.y <= info.configured_scroll_offsets.top: self.content.move_cursor_down() - def _scroll_up(self): " Scroll window up. " info = self.render_info