| | import pytest |
| | from IPython.terminal.shortcuts.auto_suggest import ( |
| | accept, |
| | accept_or_jump_to_end, |
| | accept_token, |
| | accept_character, |
| | accept_word, |
| | accept_and_keep_cursor, |
| | discard, |
| | NavigableAutoSuggestFromHistory, |
| | swap_autosuggestion_up, |
| | swap_autosuggestion_down, |
| | ) |
| | from IPython.terminal.shortcuts.auto_match import skip_over |
| | from IPython.terminal.shortcuts import create_ipython_shortcuts |
| |
|
| | from prompt_toolkit.history import InMemoryHistory |
| | from prompt_toolkit.buffer import Buffer |
| | from prompt_toolkit.document import Document |
| | from prompt_toolkit.auto_suggest import AutoSuggestFromHistory |
| |
|
| | from unittest.mock import patch, Mock |
| |
|
| |
|
| | def test_deprected(): |
| | import IPython.terminal.shortcuts.auto_suggest as iptsa |
| |
|
| | with pytest.warns(DeprecationWarning, match=r"8\.12.+accept_or_jump_to_end"): |
| | iptsa.accept_in_vi_insert_mode |
| |
|
| |
|
| | def make_event(text, cursor, suggestion): |
| | event = Mock() |
| | event.current_buffer = Mock() |
| | event.current_buffer.suggestion = Mock() |
| | event.current_buffer.text = text |
| | event.current_buffer.cursor_position = cursor |
| | event.current_buffer.suggestion.text = suggestion |
| | event.current_buffer.document = Document(text=text, cursor_position=cursor) |
| | return event |
| |
|
| |
|
| | @pytest.mark.parametrize( |
| | "text, suggestion, expected", |
| | [ |
| | ("", "def out(tag: str, n=50):", "def out(tag: str, n=50):"), |
| | ("def ", "out(tag: str, n=50):", "out(tag: str, n=50):"), |
| | ], |
| | ) |
| | def test_accept(text, suggestion, expected): |
| | event = make_event(text, len(text), suggestion) |
| | buffer = event.current_buffer |
| | buffer.insert_text = Mock() |
| | accept(event) |
| | assert buffer.insert_text.called |
| | assert buffer.insert_text.call_args[0] == (expected,) |
| |
|
| |
|
| | @pytest.mark.parametrize( |
| | "text, suggestion", |
| | [ |
| | ("", "def out(tag: str, n=50):"), |
| | ("def ", "out(tag: str, n=50):"), |
| | ], |
| | ) |
| | def test_discard(text, suggestion): |
| | event = make_event(text, len(text), suggestion) |
| | buffer = event.current_buffer |
| | buffer.insert_text = Mock() |
| | discard(event) |
| | assert not buffer.insert_text.called |
| | assert buffer.suggestion is None |
| |
|
| |
|
| | @pytest.mark.parametrize( |
| | "text, cursor, suggestion, called", |
| | [ |
| | ("123456", 6, "123456789", True), |
| | ("123456", 3, "123456789", False), |
| | ("123456 \n789", 6, "123456789", True), |
| | ], |
| | ) |
| | def test_autosuggest_at_EOL(text, cursor, suggestion, called): |
| | """ |
| | test that autosuggest is only applied at end of line. |
| | """ |
| |
|
| | event = make_event(text, cursor, suggestion) |
| | event.current_buffer.insert_text = Mock() |
| | accept_or_jump_to_end(event) |
| | if called: |
| | event.current_buffer.insert_text.assert_called() |
| | else: |
| | event.current_buffer.insert_text.assert_not_called() |
| | |
| |
|
| |
|
| | @pytest.mark.parametrize( |
| | "text, suggestion, expected", |
| | [ |
| | ("", "def out(tag: str, n=50):", "def "), |
| | ("d", "ef out(tag: str, n=50):", "ef "), |
| | ("de ", "f out(tag: str, n=50):", "f "), |
| | ("def", " out(tag: str, n=50):", " "), |
| | ("def ", "out(tag: str, n=50):", "out("), |
| | ("def o", "ut(tag: str, n=50):", "ut("), |
| | ("def ou", "t(tag: str, n=50):", "t("), |
| | ("def out", "(tag: str, n=50):", "("), |
| | ("def out(", "tag: str, n=50):", "tag: "), |
| | ("def out(t", "ag: str, n=50):", "ag: "), |
| | ("def out(ta", "g: str, n=50):", "g: "), |
| | ("def out(tag", ": str, n=50):", ": "), |
| | ("def out(tag:", " str, n=50):", " "), |
| | ("def out(tag: ", "str, n=50):", "str, "), |
| | ("def out(tag: s", "tr, n=50):", "tr, "), |
| | ("def out(tag: st", "r, n=50):", "r, "), |
| | ("def out(tag: str", ", n=50):", ", n"), |
| | ("def out(tag: str,", " n=50):", " n"), |
| | ("def out(tag: str, ", "n=50):", "n="), |
| | ("def out(tag: str, n", "=50):", "="), |
| | ("def out(tag: str, n=", "50):", "50)"), |
| | ("def out(tag: str, n=5", "0):", "0)"), |
| | ("def out(tag: str, n=50", "):", "):"), |
| | ("def out(tag: str, n=50)", ":", ":"), |
| | ], |
| | ) |
| | def test_autosuggest_token(text, suggestion, expected): |
| | event = make_event(text, len(text), suggestion) |
| | event.current_buffer.insert_text = Mock() |
| | accept_token(event) |
| | assert event.current_buffer.insert_text.called |
| | assert event.current_buffer.insert_text.call_args[0] == (expected,) |
| |
|
| |
|
| | @pytest.mark.parametrize( |
| | "text, suggestion, expected", |
| | [ |
| | ("", "def out(tag: str, n=50):", "d"), |
| | ("d", "ef out(tag: str, n=50):", "e"), |
| | ("de ", "f out(tag: str, n=50):", "f"), |
| | ("def", " out(tag: str, n=50):", " "), |
| | ], |
| | ) |
| | def test_accept_character(text, suggestion, expected): |
| | event = make_event(text, len(text), suggestion) |
| | event.current_buffer.insert_text = Mock() |
| | accept_character(event) |
| | assert event.current_buffer.insert_text.called |
| | assert event.current_buffer.insert_text.call_args[0] == (expected,) |
| |
|
| |
|
| | @pytest.mark.parametrize( |
| | "text, suggestion, expected", |
| | [ |
| | ("", "def out(tag: str, n=50):", "def "), |
| | ("d", "ef out(tag: str, n=50):", "ef "), |
| | ("de", "f out(tag: str, n=50):", "f "), |
| | ("def", " out(tag: str, n=50):", " "), |
| | |
| | ("def ", "out(tag: str, n=50):", "out(tag: "), |
| | ], |
| | ) |
| | def test_accept_word(text, suggestion, expected): |
| | event = make_event(text, len(text), suggestion) |
| | event.current_buffer.insert_text = Mock() |
| | accept_word(event) |
| | assert event.current_buffer.insert_text.called |
| | assert event.current_buffer.insert_text.call_args[0] == (expected,) |
| |
|
| |
|
| | @pytest.mark.parametrize( |
| | "text, suggestion, expected, cursor", |
| | [ |
| | ("", "def out(tag: str, n=50):", "def out(tag: str, n=50):", 0), |
| | ("def ", "out(tag: str, n=50):", "out(tag: str, n=50):", 4), |
| | ], |
| | ) |
| | def test_accept_and_keep_cursor(text, suggestion, expected, cursor): |
| | event = make_event(text, cursor, suggestion) |
| | buffer = event.current_buffer |
| | buffer.insert_text = Mock() |
| | accept_and_keep_cursor(event) |
| | assert buffer.insert_text.called |
| | assert buffer.insert_text.call_args[0] == (expected,) |
| | assert buffer.cursor_position == cursor |
| |
|
| |
|
| | def test_autosuggest_token_empty(): |
| | full = "def out(tag: str, n=50):" |
| | event = make_event(full, len(full), "") |
| | event.current_buffer.insert_text = Mock() |
| |
|
| | with patch( |
| | "prompt_toolkit.key_binding.bindings.named_commands.forward_word" |
| | ) as forward_word: |
| | accept_token(event) |
| | assert not event.current_buffer.insert_text.called |
| | assert forward_word.called |
| |
|
| |
|
| | def test_other_providers(): |
| | """Ensure that swapping autosuggestions does not break with other providers""" |
| | provider = AutoSuggestFromHistory() |
| | ip = get_ipython() |
| | ip.auto_suggest = provider |
| | event = Mock() |
| | event.current_buffer = Buffer() |
| | assert swap_autosuggestion_up(event) is None |
| | assert swap_autosuggestion_down(event) is None |
| |
|
| |
|
| | async def test_navigable_provider(): |
| | provider = NavigableAutoSuggestFromHistory() |
| | history = InMemoryHistory(history_strings=["very_a", "very", "very_b", "very_c"]) |
| | buffer = Buffer(history=history) |
| | ip = get_ipython() |
| | ip.auto_suggest = provider |
| |
|
| | async for _ in history.load(): |
| | pass |
| |
|
| | buffer.cursor_position = 5 |
| | buffer.text = "very" |
| |
|
| | up = swap_autosuggestion_up |
| | down = swap_autosuggestion_down |
| |
|
| | event = Mock() |
| | event.current_buffer = buffer |
| |
|
| | def get_suggestion(): |
| | suggestion = provider.get_suggestion(buffer, buffer.document) |
| | buffer.suggestion = suggestion |
| | return suggestion |
| |
|
| | assert get_suggestion().text == "_c" |
| |
|
| | |
| | up(event) |
| | assert get_suggestion().text == "_b" |
| |
|
| | |
| | up(event) |
| | assert get_suggestion().text == "_a" |
| |
|
| | |
| | up(event) |
| | assert get_suggestion().text == "_c" |
| |
|
| | |
| | down(event) |
| | assert get_suggestion().text == "_a" |
| |
|
| | down(event) |
| | assert get_suggestion().text == "_b" |
| |
|
| | down(event) |
| | assert get_suggestion().text == "_c" |
| |
|
| | down(event) |
| | assert get_suggestion().text == "_a" |
| |
|
| |
|
| | async def test_navigable_provider_multiline_entries(): |
| | provider = NavigableAutoSuggestFromHistory() |
| | history = InMemoryHistory(history_strings=["very_a\nvery_b", "very_c"]) |
| | buffer = Buffer(history=history) |
| | ip = get_ipython() |
| | ip.auto_suggest = provider |
| |
|
| | async for _ in history.load(): |
| | pass |
| |
|
| | buffer.cursor_position = 5 |
| | buffer.text = "very" |
| | up = swap_autosuggestion_up |
| | down = swap_autosuggestion_down |
| |
|
| | event = Mock() |
| | event.current_buffer = buffer |
| |
|
| | def get_suggestion(): |
| | suggestion = provider.get_suggestion(buffer, buffer.document) |
| | buffer.suggestion = suggestion |
| | return suggestion |
| |
|
| | assert get_suggestion().text == "_c" |
| |
|
| | up(event) |
| | assert get_suggestion().text == "_b" |
| |
|
| | up(event) |
| | assert get_suggestion().text == "_a" |
| |
|
| | down(event) |
| | assert get_suggestion().text == "_b" |
| |
|
| | down(event) |
| | assert get_suggestion().text == "_c" |
| |
|
| |
|
| | def create_session_mock(): |
| | session = Mock() |
| | session.default_buffer = Buffer() |
| | return session |
| |
|
| |
|
| | def test_navigable_provider_connection(): |
| | provider = NavigableAutoSuggestFromHistory() |
| | provider.skip_lines = 1 |
| |
|
| | session_1 = create_session_mock() |
| | provider.connect(session_1) |
| |
|
| | assert provider.skip_lines == 1 |
| | session_1.default_buffer.on_text_insert.fire() |
| | assert provider.skip_lines == 0 |
| |
|
| | session_2 = create_session_mock() |
| | provider.connect(session_2) |
| | provider.skip_lines = 2 |
| |
|
| | assert provider.skip_lines == 2 |
| | session_2.default_buffer.on_text_insert.fire() |
| | assert provider.skip_lines == 0 |
| |
|
| | provider.skip_lines = 3 |
| | provider.disconnect() |
| | session_1.default_buffer.on_text_insert.fire() |
| | session_2.default_buffer.on_text_insert.fire() |
| | assert provider.skip_lines == 3 |
| |
|
| |
|
| | @pytest.fixture |
| | def ipython_with_prompt(): |
| | ip = get_ipython() |
| | ip.pt_app = Mock() |
| | ip.pt_app.key_bindings = create_ipython_shortcuts(ip) |
| | try: |
| | yield ip |
| | finally: |
| | ip.pt_app = None |
| |
|
| |
|
| | def find_bindings_by_command(command): |
| | ip = get_ipython() |
| | return [ |
| | binding |
| | for binding in ip.pt_app.key_bindings.bindings |
| | if binding.handler == command |
| | ] |
| |
|
| |
|
| | def test_modify_unique_shortcut(ipython_with_prompt): |
| | original = find_bindings_by_command(accept_token) |
| | assert len(original) == 1 |
| |
|
| | ipython_with_prompt.shortcuts = [ |
| | {"command": "IPython:auto_suggest.accept_token", "new_keys": ["a", "b", "c"]} |
| | ] |
| | matched = find_bindings_by_command(accept_token) |
| | assert len(matched) == 1 |
| | assert list(matched[0].keys) == ["a", "b", "c"] |
| | assert list(matched[0].keys) != list(original[0].keys) |
| | assert matched[0].filter == original[0].filter |
| |
|
| | ipython_with_prompt.shortcuts = [ |
| | {"command": "IPython:auto_suggest.accept_token", "new_filter": "always"} |
| | ] |
| | matched = find_bindings_by_command(accept_token) |
| | assert len(matched) == 1 |
| | assert list(matched[0].keys) != ["a", "b", "c"] |
| | assert list(matched[0].keys) == list(original[0].keys) |
| | assert matched[0].filter != original[0].filter |
| |
|
| |
|
| | def test_disable_shortcut(ipython_with_prompt): |
| | matched = find_bindings_by_command(accept_token) |
| | assert len(matched) == 1 |
| |
|
| | ipython_with_prompt.shortcuts = [ |
| | {"command": "IPython:auto_suggest.accept_token", "new_keys": []} |
| | ] |
| | matched = find_bindings_by_command(accept_token) |
| | assert len(matched) == 0 |
| |
|
| | ipython_with_prompt.shortcuts = [] |
| | matched = find_bindings_by_command(accept_token) |
| | assert len(matched) == 1 |
| |
|
| |
|
| | def test_modify_shortcut_with_filters(ipython_with_prompt): |
| | matched = find_bindings_by_command(skip_over) |
| | matched_keys = {m.keys[0] for m in matched} |
| | assert matched_keys == {")", "]", "}", "'", '"'} |
| |
|
| | with pytest.raises(ValueError, match="Multiple shortcuts matching"): |
| | ipython_with_prompt.shortcuts = [ |
| | {"command": "IPython:auto_match.skip_over", "new_keys": ["x"]} |
| | ] |
| |
|
| | ipython_with_prompt.shortcuts = [ |
| | { |
| | "command": "IPython:auto_match.skip_over", |
| | "new_keys": ["x"], |
| | "match_filter": "focused_insert & auto_match & followed_by_single_quote", |
| | } |
| | ] |
| | matched = find_bindings_by_command(skip_over) |
| | matched_keys = {m.keys[0] for m in matched} |
| | assert matched_keys == {")", "]", "}", "x", '"'} |
| |
|
| |
|
| | def example_command(): |
| | pass |
| |
|
| |
|
| | def test_add_shortcut_for_new_command(ipython_with_prompt): |
| | matched = find_bindings_by_command(example_command) |
| | assert len(matched) == 0 |
| |
|
| | with pytest.raises(ValueError, match="example_command is not a known"): |
| | ipython_with_prompt.shortcuts = [ |
| | {"command": "example_command", "new_keys": ["x"]} |
| | ] |
| | matched = find_bindings_by_command(example_command) |
| | assert len(matched) == 0 |
| |
|
| |
|
| | def test_modify_shortcut_failure(ipython_with_prompt): |
| | with pytest.raises(ValueError, match="No shortcuts matching"): |
| | ipython_with_prompt.shortcuts = [ |
| | { |
| | "command": "IPython:auto_match.skip_over", |
| | "match_keys": ["x"], |
| | "new_keys": ["y"], |
| | } |
| | ] |
| |
|
| |
|
| | def test_add_shortcut_for_existing_command(ipython_with_prompt): |
| | matched = find_bindings_by_command(skip_over) |
| | assert len(matched) == 5 |
| |
|
| | with pytest.raises(ValueError, match="Cannot add a shortcut without keys"): |
| | ipython_with_prompt.shortcuts = [ |
| | {"command": "IPython:auto_match.skip_over", "new_keys": [], "create": True} |
| | ] |
| |
|
| | ipython_with_prompt.shortcuts = [ |
| | {"command": "IPython:auto_match.skip_over", "new_keys": ["x"], "create": True} |
| | ] |
| | matched = find_bindings_by_command(skip_over) |
| | assert len(matched) == 6 |
| |
|
| | ipython_with_prompt.shortcuts = [] |
| | matched = find_bindings_by_command(skip_over) |
| | assert len(matched) == 5 |
| |
|
| |
|
| | def test_setting_shortcuts_before_pt_app_init(): |
| | ipython = get_ipython() |
| | assert ipython.pt_app is None |
| | shortcuts = [ |
| | {"command": "IPython:auto_match.skip_over", "new_keys": ["x"], "create": True} |
| | ] |
| | ipython.shortcuts = shortcuts |
| | assert ipython.shortcuts == shortcuts |
| |
|