Show More
@@ -389,7 +389,9 b' class TerminalInteractiveShell(InteractiveShell):' | |||||
389 |
|
389 | |||
390 | def _set_autosuggestions(self, provider): |
|
390 | def _set_autosuggestions(self, provider): | |
391 | # disconnect old handler |
|
391 | # disconnect old handler | |
392 |
if self.auto_suggest and isinstance( |
|
392 | if self.auto_suggest and isinstance( | |
|
393 | self.auto_suggest, NavigableAutoSuggestFromHistory | |||
|
394 | ): | |||
393 | self.auto_suggest.disconnect() |
|
395 | self.auto_suggest.disconnect() | |
394 | if provider is None: |
|
396 | if provider is None: | |
395 | self.auto_suggest = None |
|
397 | self.auto_suggest = None | |
@@ -660,7 +662,9 b' class TerminalInteractiveShell(InteractiveShell):' | |||||
660 |
|
662 | |||
661 | def __init__(self, *args, **kwargs): |
|
663 | def __init__(self, *args, **kwargs): | |
662 | super(TerminalInteractiveShell, self).__init__(*args, **kwargs) |
|
664 | super(TerminalInteractiveShell, self).__init__(*args, **kwargs) | |
663 | self.auto_suggest: UnionType[AutoSuggestFromHistory, NavigableAutoSuggestFromHistory, None] = None |
|
665 | self.auto_suggest: UnionType[ | |
|
666 | AutoSuggestFromHistory, NavigableAutoSuggestFromHistory, None | |||
|
667 | ] = None | |||
664 | self._set_autosuggestions(self.autosuggestions_provider) |
|
668 | self._set_autosuggestions(self.autosuggestions_provider) | |
665 | self.init_prompt_toolkit_cli() |
|
669 | self.init_prompt_toolkit_cli() | |
666 | self.init_term_title() |
|
670 | self.init_term_title() |
@@ -58,7 +58,6 b' class NavigableAutoSuggestFromHistory(AutoSuggestFromHistory):' | |||||
58 | self, text: str, skip_lines: float, history: History, previous: bool |
|
58 | self, text: str, skip_lines: float, history: History, previous: bool | |
59 | ): |
|
59 | ): | |
60 | line_number = -1 |
|
60 | line_number = -1 | |
61 |
|
||||
62 | for string in reversed(list(history.get_strings())): |
|
61 | for string in reversed(list(history.get_strings())): | |
63 | for line in reversed(string.splitlines()): |
|
62 | for line in reversed(string.splitlines()): | |
64 | line_number += 1 |
|
63 | line_number += 1 | |
@@ -167,7 +166,7 b' def accept_and_keep_cursor(event: KeyPressEvent):' | |||||
167 |
|
166 | |||
168 |
|
167 | |||
169 | def accept_and_move_cursor_left(event: KeyPressEvent): |
|
168 | def accept_and_move_cursor_left(event: KeyPressEvent): | |
170 | """Accept autosuggestion and move cursor left""" |
|
169 | """Accept autosuggestion and move cursor left in place""" | |
171 | accept_and_keep_cursor(event) |
|
170 | accept_and_keep_cursor(event) | |
172 | nc.backward_char(event) |
|
171 | nc.backward_char(event) | |
173 |
|
172 |
@@ -2,8 +2,18 b' import pytest' | |||||
2 | from IPython.terminal.shortcuts.auto_suggest import ( |
|
2 | from IPython.terminal.shortcuts.auto_suggest import ( | |
3 | accept_in_vi_insert_mode, |
|
3 | accept_in_vi_insert_mode, | |
4 | accept_token, |
|
4 | accept_token, | |
|
5 | accept_character, | |||
|
6 | accept_word, | |||
|
7 | accept_and_keep_cursor, | |||
|
8 | NavigableAutoSuggestFromHistory, | |||
|
9 | swap_autosuggestion_up, | |||
|
10 | swap_autosuggestion_down, | |||
5 | ) |
|
11 | ) | |
6 |
|
12 | |||
|
13 | from prompt_toolkit.history import InMemoryHistory | |||
|
14 | from prompt_toolkit.shortcuts import PromptSession | |||
|
15 | from prompt_toolkit.buffer import Buffer | |||
|
16 | ||||
7 | from unittest.mock import patch, Mock |
|
17 | from unittest.mock import patch, Mock | |
8 |
|
18 | |||
9 |
|
19 | |||
@@ -81,6 +91,59 b' def test_autosuggest_token(text, suggestion, expected):' | |||||
81 | assert event.current_buffer.insert_text.call_args[0] == (expected,) |
|
91 | assert event.current_buffer.insert_text.call_args[0] == (expected,) | |
82 |
|
92 | |||
83 |
|
93 | |||
|
94 | @pytest.mark.parametrize( | |||
|
95 | "text, suggestion, expected", | |||
|
96 | [ | |||
|
97 | ("", "def out(tag: str, n=50):", "d"), | |||
|
98 | ("d", "ef out(tag: str, n=50):", "e"), | |||
|
99 | ("de ", "f out(tag: str, n=50):", "f"), | |||
|
100 | ("def", " out(tag: str, n=50):", " "), | |||
|
101 | ], | |||
|
102 | ) | |||
|
103 | def test_accept_character(text, suggestion, expected): | |||
|
104 | event = make_event(text, len(text), suggestion) | |||
|
105 | event.current_buffer.insert_text = Mock() | |||
|
106 | accept_character(event) | |||
|
107 | assert event.current_buffer.insert_text.called | |||
|
108 | assert event.current_buffer.insert_text.call_args[0] == (expected,) | |||
|
109 | ||||
|
110 | ||||
|
111 | @pytest.mark.parametrize( | |||
|
112 | "text, suggestion, expected", | |||
|
113 | [ | |||
|
114 | ("", "def out(tag: str, n=50):", "def "), | |||
|
115 | ("d", "ef out(tag: str, n=50):", "ef "), | |||
|
116 | ("de", "f out(tag: str, n=50):", "f "), | |||
|
117 | ("def", " out(tag: str, n=50):", " "), | |||
|
118 | # (this is why we also have accept_token) | |||
|
119 | ("def ", "out(tag: str, n=50):", "out(tag: "), | |||
|
120 | ], | |||
|
121 | ) | |||
|
122 | def test_accept_word(text, suggestion, expected): | |||
|
123 | event = make_event(text, len(text), suggestion) | |||
|
124 | event.current_buffer.insert_text = Mock() | |||
|
125 | accept_word(event) | |||
|
126 | assert event.current_buffer.insert_text.called | |||
|
127 | assert event.current_buffer.insert_text.call_args[0] == (expected,) | |||
|
128 | ||||
|
129 | ||||
|
130 | @pytest.mark.parametrize( | |||
|
131 | "text, suggestion, expected, cursor", | |||
|
132 | [ | |||
|
133 | ("", "def out(tag: str, n=50):", "def out(tag: str, n=50):", 0), | |||
|
134 | ("def ", "out(tag: str, n=50):", "out(tag: str, n=50):", 4), | |||
|
135 | ], | |||
|
136 | ) | |||
|
137 | def test_accept_and_keep_cursor(text, suggestion, expected, cursor): | |||
|
138 | event = make_event(text, cursor, suggestion) | |||
|
139 | buffer = event.current_buffer | |||
|
140 | buffer.insert_text = Mock() | |||
|
141 | accept_and_keep_cursor(event) | |||
|
142 | assert buffer.insert_text.called | |||
|
143 | assert buffer.insert_text.call_args[0] == (expected,) | |||
|
144 | assert buffer.cursor_position == cursor | |||
|
145 | ||||
|
146 | ||||
84 | def test_autosuggest_token_empty(): |
|
147 | def test_autosuggest_token_empty(): | |
85 | full = "def out(tag: str, n=50):" |
|
148 | full = "def out(tag: str, n=50):" | |
86 | event = make_event(full, len(full), "") |
|
149 | event = make_event(full, len(full), "") | |
@@ -92,3 +155,79 b' def test_autosuggest_token_empty():' | |||||
92 | accept_token(event) |
|
155 | accept_token(event) | |
93 | assert not event.current_buffer.insert_text.called |
|
156 | assert not event.current_buffer.insert_text.called | |
94 | assert forward_word.called |
|
157 | assert forward_word.called | |
|
158 | ||||
|
159 | ||||
|
160 | async def test_navigable_provider(): | |||
|
161 | provider = NavigableAutoSuggestFromHistory() | |||
|
162 | history = InMemoryHistory(history_strings=["very_a", "very", "very_b", "very_c"]) | |||
|
163 | buffer = Buffer(history=history) | |||
|
164 | ||||
|
165 | async for _ in history.load(): | |||
|
166 | pass | |||
|
167 | ||||
|
168 | buffer.cursor_position = 5 | |||
|
169 | buffer.text = "very" | |||
|
170 | ||||
|
171 | up = swap_autosuggestion_up(provider) | |||
|
172 | down = swap_autosuggestion_down(provider) | |||
|
173 | ||||
|
174 | event = Mock() | |||
|
175 | event.current_buffer = buffer | |||
|
176 | ||||
|
177 | def get_suggestion(): | |||
|
178 | suggestion = provider.get_suggestion(buffer, buffer.document) | |||
|
179 | buffer.suggestion = suggestion | |||
|
180 | return suggestion | |||
|
181 | ||||
|
182 | assert get_suggestion().text == "_c" | |||
|
183 | ||||
|
184 | # should go up | |||
|
185 | up(event) | |||
|
186 | assert get_suggestion().text == "_b" | |||
|
187 | ||||
|
188 | # should skip over 'very' which is identical to buffer content | |||
|
189 | up(event) | |||
|
190 | assert get_suggestion().text == "_a" | |||
|
191 | ||||
|
192 | # should cycle back to beginning | |||
|
193 | up(event) | |||
|
194 | assert get_suggestion().text == "_c" | |||
|
195 | ||||
|
196 | # should cycle back through end boundary | |||
|
197 | down(event) | |||
|
198 | assert get_suggestion().text == "_a" | |||
|
199 | ||||
|
200 | down(event) | |||
|
201 | assert get_suggestion().text == "_b" | |||
|
202 | ||||
|
203 | down(event) | |||
|
204 | assert get_suggestion().text == "_c" | |||
|
205 | ||||
|
206 | down(event) | |||
|
207 | assert get_suggestion().text == "_a" | |||
|
208 | ||||
|
209 | ||||
|
210 | def test_navigable_provider_connection(): | |||
|
211 | provider = NavigableAutoSuggestFromHistory() | |||
|
212 | provider.skip_lines = 1 | |||
|
213 | ||||
|
214 | session_1 = PromptSession() | |||
|
215 | provider.connect(session_1) | |||
|
216 | ||||
|
217 | assert provider.skip_lines == 1 | |||
|
218 | session_1.default_buffer.on_text_insert.fire() | |||
|
219 | assert provider.skip_lines == 0 | |||
|
220 | ||||
|
221 | session_2 = PromptSession() | |||
|
222 | provider.connect(session_2) | |||
|
223 | provider.skip_lines = 2 | |||
|
224 | ||||
|
225 | assert provider.skip_lines == 2 | |||
|
226 | session_2.default_buffer.on_text_insert.fire() | |||
|
227 | assert provider.skip_lines == 0 | |||
|
228 | ||||
|
229 | provider.skip_lines = 3 | |||
|
230 | provider.disconnect() | |||
|
231 | session_1.default_buffer.on_text_insert.fire() | |||
|
232 | session_2.default_buffer.on_text_insert.fire() | |||
|
233 | assert provider.skip_lines == 3 |
General Comments 0
You need to be logged in to leave comments.
Login now